/**
* // todo: standards
*/
#ifndef PVR_H
#define PVR_H

#include <string>
#include <vector>
#include <kos.h>
#include <kmg/kmg.h>
#include <assert.h>
#include "GREEN/Image/Image.h"
#include "ORANGE/QuickMath/VectorMath.h"
#include "ResourceManager/resmgr.h"

using namespace std;

#define MAX_TEX_DIMENSION 1024
#define MIN_TEX_DIMENSION 8
#define TEXTURE_GRID_SIZE 256
#define LAYER_SCALE 1000.f

#define PVR_TEXTURE_CUTSCENE_BG 0
#define PVR_TEXTURE_SPLASH_FIRST 1
#define PVR_TEXTURE_SPLASH_SECOND 2
#define PVR_TEXTURE_SPLASH_THIRD 3
#define PVR_TEXTURE_SPLASH_KOS 4
//#define PVR_TEXTURE_BUTTONS 5
#define PVR_TEXTURE_PARTICLES_AAN 6
#define PVR_TEXTURE_SMOKE_AAN 7
#define PVR_TEXTURE_EXPLODE_AAN 8
#define PVR_TEXTURE_GEXPLODE_AAN 9
#define PVR_TEXTURE_FIRE_AAN 10
#define PVR_TEXTURE_TOPBAR 11
#define PVR_TEXTURE_CONTROLLER 12
#define PVR_TEXTURE_MINE_TIMED 13
#define PVR_TEXTURE_MINE_PROXIMITY 14
#define PVR_TEXTURE_MINE_BETTY 15
#define PVR_TEXTURE_MENU_BG 16
#define PVR_TEXTURE_MENU_BULLET 17
#define PVR_TEXTURE_MENU_BUTTON 18
#define PVR_TEXTURE_MENU_BUTTON_L 19
#define PVR_TEXTURE_MENU_BUTTON_R 20
#define PVR_TEXTURE_MENU_BAR 21
#define PVR_TEXTURE_MENU_TEXT_BG 22
#define PVR_TEXTURE_TOOLBAR 23
#define PVR_TEXTURE_TARGET_FIRST 24
#define PVR_TEXTURE_TARGET_SECOND 25
#define PVR_TEXTURE_TARGET_THIRD 26
#define PVR_TEXTURE_HOOD_BG 27
#define PVR_TEXTURE_PROGRESS_BAR 28
#define PVR_TEXTURE_SHADOW 29
#define PVR_TEXTURE_SMALL_SHADOW 30
#define PVR_TEXTURE_FIRE_TOP 33
#define PVR_TEXTURE_SPLASH_CHINCHILLA 34
#define PVR_TEXTURE_SPLASH_WAVE 35
#define PVR_TEXTURE_PATCH_1 36
#define PVR_TEXTURE_PATCH_2 37
#define PVR_TEXTURE_PATCH_3 38

#define PACK_ARGB8888(a, r, g, b) (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF))

#define PVR_MAX_VERTS		31000
#define PVR_MAX_MESH_VERTS	28000

// RSPiX Forward declarations.
class RRect;
class CSprite2;

namespace PVR
{
	// Forward declarations.
	class cTextureTile;
	class cTextureFactory;
	class cRenderMgr;
	class cDisplayListQueue;
	class cGuiTextString;
	class cTextureRepository;
	class cTextureTile;
	class cRenderMgr;

	/// <summary>
	///	KMG image wrapper class.
	/// </summary>
	class cKmgImg
	{
		friend class PVR::cTextureTile;

	private:
		kos_img_t m_KosImg;

	public:
		cKmgImg() {} // Empty constructor for ResMgr compatibility
		cKmgImg(const char *);
		~cKmgImg();

		// Loads a KMG from a path or an resource manager's RFile.
		int KmgToImg(const char *);
		int KmgToImg(RFile *);

		// File-load callback for resource manager.
		int16_t Load(RFile *pcf);
	};

	/// <summary>
	/// PVR texture object class.
	/// </summary>
	class cTexture
	{
		friend class PVR::cTextureFactory;
		friend class PVR::cRenderMgr;
		friend class PVR::cTextureRepository;
		friend class PVR::cTextureTile;

	private:
				
		bool m_bHasTileGrid = false; // The texture has multiple tiles.

		uint8_t m_ucImageType = PAL8;
		uint8_t m_ucPaletteNum = 0;
		uint16_t m_szId = 0;

		// Texture/image dimensions.
		uint16_t m_szTextureWidth = 0;
		uint16_t m_szTextureHeight = 0;
		int16_t m_szImageWidth = 0;
		int16_t m_szImageHeight = 0;
		int16_t m_sNumTiles = 0;

		void AddTile(cTextureTile *, uint16_t = 0);
		void SetGrid(uint8_t, uint8_t);

	public:
		// Texture types.
		typedef enum eTextureTypes
		{
			PAL4,  // 4-bit paletted texture.
			PAL8,  // 8-bit paletted texture.
			RGB16, // 16-bit (A)RGB texture.
		} Type;


		// List of texture tiles which comprise the parent texture object.
		cTextureTile **m_pTextureTiles;

		// Kerning data for fontset textures.
		int16_t m_sFontKernL = 0;
		int16_t m_sFontKernR = 0;
		int16_t m_sFontWidth = 0;
		int16_t m_sFontTrueWidth = 0;

		bool m_bIsLocked = false; // Texture is deletable.

		// Constructors/destructor
		cTexture();
		cTexture(RImage *, uint8_t = 0);
		cTexture(char *, uint8_t, uint8_t, bool = false);
		cTexture(RResMgr *, char *, uint8_t, uint8_t, bool = false);
		cTexture(uint16_t, uint16_t, uint8_t = 1);
		cTexture(uint8_t *, uint16_t, uint16_t, uint8_t = 1);
		~cTexture();

		// Getters
		uint16_t GetTextureWidth(void);
		uint16_t GetTextureHeight(void);
		int16_t GetImageWidth(void);
		int16_t GetImageHeight(void);
	};

	/// <summary>
	/// PVR texture tile object.
	/// </summary>
	class cTextureTile
	{
		friend class PVR::cTexture;
		friend class PVR::cRenderMgr;
		friend class PVR::cTextureFactory;

	private:
		pvr_poly_cxt_t cxt;
		pvr_poly_hdr_t hdr;

		pvr_poly_cxt_t cxtOP;
		pvr_poly_hdr_t hdrOP;
		pvr_poly_cxt_t cxtTR;
		pvr_poly_hdr_t hdrTR;

		//todo:
		//pvr_ptr_t m_pPvrTexture = nullptr;
		cTexture *m_pParentTexture = nullptr;
		bool m_bIsVQ = false;	 // Is a VQ texture.
		bool m_bIsARGB4 = false; // ARGB4444
		bool m_bIsARGB1 = false; // ARGB1555
		uint8_t m_ucImageType = PVR::cTexture::PAL8;
		uint8_t m_ucPaletteNum = 0;
		uint16_t m_szId = 0;	
		uint16_t m_szTileWidth = 0;
		uint16_t m_szTileHeight = 0;
		uint16_t m_szImageWidth = 0;
		uint16_t m_szImageHeight = 0;
		
	public:
		pvr_ptr_t m_pPvrTexture = nullptr;
		// Constructors/destructor.
		cTextureTile(cKmgImg *kosImg, bool isAlpha = false);
		cTextureTile(RImage *, uint8_t = 1, uint8_t = 0);
		cTextureTile(uint16_t width, uint16_t height, uint8_t = 1, uint8_t ucPalNum = 0);
		~cTextureTile();

		// Pastes and RImage onto the texture.
		void PasteImage(RImage *, int16_t = 0, int16_t = 0, uint8_t type = cTexture::PAL8);

		inline __attribute__((always_inline)) pvr_poly_hdr_t *GetPvrHdr(int8_t cMode = 0)
		{
			if (cMode)
			{
				return &hdrTR;
			}
			else return &hdrOP;
		}
	};

	/// <summary>
	/// A simple class for representing a batched quad
	/// </summary>
	class cBatchedQuad
	{
		friend class PVR::cDisplayListQueue;
		friend class PVR::cRenderMgr;

		private:
			bool m_bIsPersistent = false;
			int16_t m_iDrawQueue = 0;
			int16_t m_sX;
			int16_t m_sY;
			uint16_t m_sW;
			uint16_t m_sH;
			uint8_t m_ucTileId = 0;
			cTexture *m_pTexture;																		
			float m_fStartU;
			float m_fStartV;
			float m_fEndU;
			float m_fEndV;
			uint32_t m_ulColour;
			uint32_t m_sLayer;

		public:
			cBatchedQuad(
				int16_t sX, int16_t sY, 
				uint16_t sW, uint16_t sH, 
				cTexture *pTexture, uint8_t ucTileId, 
				float fStartU, float fStartV, 
				float fEndU, float fEndV,
				uint32_t ulColour, int16_t sLayer);

			cBatchedQuad(
				int16_t sX, int16_t sY, 
				uint16_t sW, uint16_t sH,				
				uint32_t ulColour, int16_t sLayer);			
	};

	/// <summary>
	/// Display list queues - Used for batching & displaying polys in order. Only used in batched mode.
	/// </summary>
	class cDisplayListQueue
	{
		friend class PVR::cRenderMgr;

	private:
		bool m_bLocked = false;
		vector<cBatchedQuad *> m_vQuads;
		
	public:
		cDisplayListQueue();
		void Add(cBatchedQuad *);
		void Clear();
		void Lock();
		void Unlock();
		bool IsLocked();
	};

	/// <summary>
	/// GUI text character class.
	/// </summary>
	class cGuiChar
	{
		friend class PVR::cGuiTextString;

	private:
		uint16_t m_usCharacterId; // Character code.
		cTexture *m_pTexture;	  // Texture for the character.
		int16_t m_sX;			  // Screen-space X & Y coordinates.
		int16_t m_sY;
		int16_t m_sWidth; // Screen-space dimensions of the drawn character.
		int16_t m_sHeight;
		// todo?
		float m_fR = 0.f; // RGB values for the rendered character.
		float m_fG = 0.f;
		float m_fB = 0.f;
		uint32_t m_ulColour = 0xFFFFFFFF;
		int8_t m_cLayerOffset = 0;

	public:
		cGuiChar(uint16_t, int16_t, int16_t, int16_t, int16_t, cTexture *);
		~cGuiChar();
		void Print(int16_t = 0, int16_t = 0); // Draws the character to the screen.
	};

	/// <summary>
	/// GUI text string class.
	/// </summary>
	class cGuiTextString
	{
	private:		
		cGuiChar ** m_pCharacters;
		int16_t m_sCurrentChar;
		int16_t m_sNumChars;

	public:	
		cGuiTextString();
		~cGuiTextString();		
		void Print(int16_t = 0, int16_t = 0);
		void Flush();
		void Add(uint16_t, int16_t, int16_t, int16_t, int16_t, cTexture *, float, float, float, int8_t = 0);
		void Add(uint16_t, int16_t, int16_t, int16_t, int16_t, cTexture *, uint32_t, int8_t = 0);
	};

	/// <summary>
	/// Texture factory class.
	/// </summary>
	class cTextureFactory
	{
	public:
		static RResMgr *pActiveResMgr;

		/// <summary>
		/// Sets the active resource manager object for loading from a .SAK.
		/// </summary>
		static void SetResMgr(RResMgr *pResMgr)
		{
			pActiveResMgr = pResMgr;
		}

		/// <summary>
		/// Unsets the active resource manager object for loading from a directory.
		/// </summary>
		static void UnsetResMgr(void)
		{
			pActiveResMgr = nullptr;
		}

		/// <summary>
		/// Returns a new texture object without any bitmap data.
		/// </summary>
		static cTexture *Stub(uint8_t ucImageType, uint16_t sImageWidth, uint16_t sImageHeight)
		{
			cTexture *pTexture = new cTexture();
			cTextureTile *pTile = new cTextureTile(sImageWidth, sImageHeight, ucImageType);

			pTile->m_pPvrTexture = pvr_mem_malloc(pTile->m_szTileWidth * pTile->m_szTileHeight * ucImageType);

			pTexture->m_ucImageType = ucImageType;
			pTexture->m_szImageWidth = pTile->m_szImageWidth;
			pTexture->m_szImageHeight = pTile->m_szImageHeight;
			pTexture->m_szTextureWidth = pTile->m_szTileWidth;
			pTexture->m_szTextureHeight = pTile->m_szTileHeight;

			pTile->m_pParentTexture = pTexture;
			
			pTexture->AddTile(pTile);			
			return pTexture;
		}

		/// <summary>
		///	Returns a new texture object populated by an existing texture tile.
		/// </summary>
		static cTexture *FromTile(cTextureTile *pTile)
		{
			cTexture *pTexture = new cTexture();

			pTexture->m_szImageWidth = pTile->m_szImageWidth;
			pTexture->m_szImageHeight = pTile->m_szImageHeight;
			pTexture->m_szTextureWidth = pTile->m_szTileWidth;
			pTexture->m_szTextureHeight = pTile->m_szTileHeight;

			pTexture->m_ucPaletteNum = pTile->m_ucPaletteNum;
			pTexture->m_ucImageType = pTile->m_ucImageType;

			pTile->m_pParentTexture = pTexture;

			pTexture->AddTile(pTile);
			return pTexture;
		}

		/// <summary>
		///	Returns a new texture object populated by a given RImage object.
		/// </summary>
		static cTexture *FromRimage(RImage *pImg, uint8_t ucPalNum = 0)
		{
			if (pImg->m_type != RImage::BMP8)
			{
				pImg->Convert(RImage::BMP8);
			}
			return new cTexture(pImg, ucPalNum);
		}

		/// <summary>
		///	Returns a new texture object populated by an RImage file at the given path.
		/// </summary>
		static cTexture *FromRimage(char *path, uint8_t ucPalNum = 0)
		{
			RImage *pImg;
			cTexture *pTexture;

			if (rspGetResource(pActiveResMgr, path, &pImg, RFile::LittleEndian) == 0)
			{
				if (pImg->m_type != RImage::BMP8)
				{
					pImg->Convert(RImage::BMP8);
				}
				pTexture = new cTexture(pImg, ucPalNum);
				rspReleaseAndPurgeResource(pActiveResMgr, &pImg);
				return pTexture;
			}
			else
			{				
				return nullptr;
			}
		}

		/// <summary>
		///	Returns a new texture object populated by an RImage instantiated with a given RFile object.
		/// </summary>
		static cTexture *FromRimage(RFile *pFile, uint8_t ucPalNum = 0)
		{
			RImage *pImg = new RImage;
			cTexture *pTexture;
			pImg->Load(pFile);
			pImg->Convert(RImage::BMP8);
			pTexture = FromRimage(pImg, ucPalNum);
			delete pImg;
			return pTexture;
		}

		/// <summary>
		///	Returns a new texture object populated by a KMG tileset at the given path.
		/// </summary>
		static cTexture *FromKmg(char *path, uint8_t columns, uint8_t rows, bool isAlpha = false)
		{
			cTexture *pTexture = nullptr;
			if (pActiveResMgr != nullptr)
			{
				pTexture = new cTexture(pActiveResMgr, path, columns, rows, isAlpha);
			}
			else
			{
				pTexture = new cTexture(path, columns, rows, isAlpha);
			}

			return pTexture;
		}

		/// <summary>
		///	Returns a new texture object populated by a KMG tileset at the given path and overrides the image width/height.
		/// </summary>
		static cTexture *FromKmg(char *path, uint8_t columns, uint8_t rows, uint16_t width, uint16_t height, bool isAlpha = false)
		{
			cTexture *pTexture = FromKmg(path, columns, rows, isAlpha);
			pTexture->m_szImageWidth = width;
			pTexture->m_szImageHeight = height;
			return pTexture;
		}
	};

	/// <summary>
	/// Texture repository class used for storing and accessing textures using a given ID.
	/// </summary>
	class cTextureRepository
	{

	private:		
		cTexture *m_Textures[512];
		
	public:
		cTextureRepository();
		~cTextureRepository();

		// Add an existing texture to the repository.
		//	cTexture *Add(const string &, cTexture *);
		cTexture *Add(uint16_t, cTexture *);

		// Create a new texture and add to the repository.
		cTexture *Create(uint16_t);
		//	cTexture *Create(const string &);

		// Get a texture from the repository.
		cTexture *Get(uint16_t);
		//	cTexture *Get(const string &);

		// Remove textures from the repository.
		//	void Delete(const string &);
		void Delete(uint16_t index);
		void Clear();
	};

	/// <summary>
	///	PVR render manager class.
	/// </summary>
	class cRenderMgr
	{

	private:
		static const int NUM_OPAQUE_LAYERS = 1;
		static const int NUM_PUNCHTHRU_LAYERS = 16; // Puncthrough layers are currently just regular transparency layers.
		static const int NUM_TRANSPARENCY_LAYERS = 16;

		// Batched mode draw queues.
		cDisplayListQueue *opaqueList[NUM_OPAQUE_LAYERS];
		cDisplayListQueue *punchThruList[NUM_PUNCHTHRU_LAYERS];
		cDisplayListQueue *transparencyQueueList[NUM_TRANSPARENCY_LAYERS];

		int16_t m_sSceneClipX = 0;
		int16_t m_sSceneClipY = 40;
		int16_t m_sSceneClipW = 640;
		int16_t m_sSceneClipH = 400;

	public:
		// todo: ??
		pvr_poly_cxt_t m_cxtFlatOpCol;
		pvr_poly_cxt_t m_cxtFlatTrCol;
		pvr_poly_hdr_t m_hdrFlatOpCol;		
		pvr_poly_hdr_t m_hdrFlatTrCol;
		bool m_bLoadedCtxOp = false;
		bool m_bLoadedCtxTr = false;

		// todo?		
		int32_t m_lNumVerts = 0;
		int32_t m_lNumPrevVerts = 0;
		int32_t m_lLastFrameStart = 0;
		int32_t m_lLastFrameTicks = 0;

		// todo?
		bool m_bFrameSkip = false;			
		bool m_bIncFrameTokenLevel = false;

		int8_t m_cIssuedFrameToken = 0;
		int8_t m_cCurrentFrameToken = 0;
		int8_t m_cCurrentFrameTokenLevel = 0;		
		int16_t m_sFrameTokenClearCount = 0;
		int32_t m_lFrameTokenCumulativeFrames = 0;
		

		// Scene layer enums
		typedef enum
		{
			// First entry must start at 0!
			LayerBg = 0,
			LayerSprite1,
			LayerOpaque1,
			LayerSprite2,
			LayerAlpha1,
			LayerSprite3,
			LayerOpaque2,
			LayerSprite4,
			LayerAlpha2,
			LayerSprite5,
			LayerOpaque3,
			LayerSprite6,
			LayerAlpha3,
			LayerSprite7,
			LayerOpaque4,
			LayerSprite8,
			LayerAlpha4,
			LayerSprite9,
			LayerOpaque5,
			LayerSprite10,
			LayerAlpha5,
			LayerSprite11,
			LayerOpaque6,
			LayerSprite12,
			LayerAlpha6,
			LayerSprite13,
			LayerOpaque7,
			LayerSprite14,
			LayerAlpha7,
			LayerSprite15,
			LayerOpaque8,
			LayerSprite16,
			LayerHud,
			TotalLayers // This must be the last entry so it gets set to the total number of layers.
		} Layer;

		// Scene draw modes.
		typedef enum
		{
			Batched = -1,						   // Scene items are batched and may be submitted out-of-order as the scene is drawn according to layer.
			DirectOpaque = PVR_LIST_OP_POLY,	   // Opaque draw list - Items are drawn as they are submitted.
			DirectTransparent = PVR_LIST_TR_POLY,  // Transparent draw list - Items are drawn as they are submitted.
			DirectPunchThrough = PVR_LIST_PT_POLY, // Not currently used.
		} DrawMode;

		// Active draw mode.
		int8_t m_szDrawMode = Batched;

		// Palette IDs.
		typedef enum
		{
			DefaultPalette = 0,
			MenuPalette,
			FontPalette,
			HudPalette,
		} Palette;

		// Global RGB scale values - Mostly used for fading colour channels.
		float m_fGlobalRScale = 1.f;
		float m_fGlobalGScale = 1.f;
		float m_fGlobalBScale = 1.f;

		// Hardware palettes.
		RPal *m_pPaletteDefault = nullptr;
		RPal *m_pPaletteMenu = nullptr;
		RPal *m_pPaletteHud = nullptr;

		// Global texture repository.
		cTextureRepository m_TextureRepository;

		// Global GUI text target.
		cGuiTextString *m_pGuiTextString;

		// Render-to-texture target.
		cTexture *renderTarget = nullptr;

		// Constructor.
		cRenderMgr();

		// Draws submitted batched geometry, sorted and ordered by layer
		void RenderBatchedFrame();

		// Empties batched draq queues.
		void FlushBatchedDrawQueues();
		void FlushBatchedDrawQueue(int16_t);

		// Clears the screen and optionally flushes draw queues.
		void ClearScreen(bool = false);

		// Initialises the render-to-texture target.
		void InitRenderTarget(void);

		// Returns the requested draw queue.
		cDisplayListQueue *GetQueue(int16_t, float = 1.0f);		

		inline __attribute__((always_inline))  void SetClip(int16_t sX, int16_t sY, int16_t sW, int16_t sH)
		{
			m_sSceneClipX = sX;
			m_sSceneClipY = sY;
			m_sSceneClipW = sW;
			m_sSceneClipH = sH;
		}

		inline __attribute__((always_inline)) int8_t IssueFrameToken()
		{
			//m_cIssuedFrameToken = 5;
			if (++m_cIssuedFrameToken == 4)
			{
				m_cIssuedFrameToken = 1;
			}
			return m_cIssuedFrameToken;
		}

		inline __attribute__((always_inline)) int8_t CurrentFrameTokenLevel()
		{			
			return m_cCurrentFrameTokenLevel;
		}

		inline __attribute__((always_inline)) int8_t CurrentFrameToken()
		{								
			// if cumulative of last n frames < 9500, switch off

			if (m_cCurrentFrameTokenLevel)
			{
				return m_cCurrentFrameToken;
			}			
			else 
			{
				return 0;
			}			
		}
		
		// Draw coloured quads.
		void DrawQuad(uint32_t, int16_t, int16_t, uint16_t, uint16_t, int16_t);
		void DrawQuad(uint32_t, float, float, uint16_t, uint16_t, int16_t);

		// Draw textured quads.
		void DrawQuad(cTexture *, int16_t, int16_t, uint16_t, uint16_t, int16_t = 0, float = 1.0f, float = 1.0f, float = 1.0f, float = 1.0f);
		void DrawQuad(cTexture *, uint8_t, float, float, float, float, int16_t, int16_t, uint16_t, uint16_t, int16_t = 0, float = 1.0f, float = 1.0f, float = 1.0f, float = 1.0f);

		// Draw coloured quads.
		void DrawQuadDirect(uint32_t, int16_t, int16_t, uint16_t, uint16_t, int16_t);

		// Draw textured quads.

		/// <summary>
		/// Draws a textured quad filled using a given segment of a texture.
		/// </summary>
		inline __attribute__((always_inline)) void DrawQuadDirect(cTexture *pTexture, uint8_t ucTileId, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t sLayer, float fOpacity = 1.f, uint32_t colour = 0xFFFFFFFF)
		{
			cTextureTile *pTile = pTexture->m_pTextureTiles[ucTileId];

			switch (m_szDrawMode)
			{
				case DirectOpaque:
					pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
				break;
				case DirectTransparent:
					pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
				break;
			}					

			// Hack to ensure 2D & 3D objects are layered correctly
			float sceneZ = 0.f;

			if (fOpacity < 1.0f)
			{
				if (sLayer != LayerBg)
				{
					sceneZ = (float(sLayer) + 1.99f) * LAYER_SCALE;
				}
				else
				{
					sceneZ = (sLayer + 1) * LAYER_SCALE;
				}
			}
			else
			{
				sceneZ = (sLayer + 1) * LAYER_SCALE;
			}

			pvr_dr_state_t state;
			pvr_vertex_t *vert;
			pvr_dr_init(state);

			// top left 0,0
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX;
			vert->y = sY;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fStartU;
			vert->v = fStartV;
			pvr_dr_commit(vert);

			// top right
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX + sW;
			vert->y = sY;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fEndU;
			vert->v = fStartV;
			pvr_dr_commit(vert);

			// bottom left
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX;
			vert->y = sY + sH;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fStartU;
			vert->v = fEndV;
			pvr_dr_commit(vert);

			// bottom right
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX_EOL;
			vert->x = sX + sW;
			vert->y = sY + sH;
			vert->z = (sLayer + 1) * LAYER_SCALE;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fEndU;
			vert->v = fEndV;
			pvr_dr_commit(vert);

			m_lNumVerts += 4;
		}

		/// <summary>
		/// Draws a textured quad, scaled to the given width & height.
		/// </summary>
		inline __attribute__((always_inline)) void DrawQuadDirect(cTexture *pTexture, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer, float fOpacity = 1.f, uint32_t colour = 0xFFFFFFFF)
		{
			uint16_t tileCount = pTexture->m_sNumTiles;

			if (tileCount == 1)
			{
				cTextureTile *pTile = pTexture->m_pTextureTiles[0];
				
				switch (m_szDrawMode)
				{
					case DirectOpaque:
						pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
					break;
					case DirectTransparent:
						pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
					break;
				}
		
				// todo: fast div?
				float adjustedMaxU = (1.0 / pTile->m_szTileWidth) * pTile->m_szImageWidth;
				float adjustedMaxV = (1.0 / pTile->m_szTileHeight) * pTile->m_szImageHeight;

				// Hack to ensure 2D & 3D objects are layered correctly
				float sceneZ = ((fOpacity < 1.0f) ? (float(layer) + 1.99f) * LAYER_SCALE : (layer + 1) * LAYER_SCALE);

				pvr_dr_state_t state;
				pvr_vertex_t *vert;
				pvr_dr_init(state);

				// top left 0,0
				vert = pvr_dr_target(state);
				vert->flags = PVR_CMD_VERTEX;
				vert->x = sX;
				vert->y = sY;
				vert->z = sceneZ;
				vert->argb = colour;
				vert->u = 0;
				vert->v = 0;
				pvr_dr_commit(vert);

				// top right
				vert = pvr_dr_target(state);
				vert->flags = PVR_CMD_VERTEX;
				vert->x = sX + sW;
				vert->y = sY;
				vert->z = sceneZ;
				vert->argb = colour;
				vert->u = adjustedMaxU;
				vert->v = 0;
				pvr_dr_commit(vert);

				// bottom left
				vert = pvr_dr_target(state);
				vert->flags = PVR_CMD_VERTEX;
				vert->x = sX;
				vert->y = sY + sH;
				vert->z = sceneZ;
				vert->argb = colour;
				vert->u = 0;
				vert->v = adjustedMaxV;
				pvr_dr_commit(vert);

				// bottom right
				vert = pvr_dr_target(state);
				vert->flags = PVR_CMD_VERTEX_EOL;
				vert->x = sX + sW;
				vert->y = sY + sH;
				vert->z = sceneZ;
				vert->argb = colour;
				vert->u = adjustedMaxU;
				vert->v = adjustedMaxV;
				pvr_dr_commit(vert);

				m_lNumVerts += 4;
			}
			else if (tileCount > 1)
			{
				for (int index = 0; index < tileCount; index++)
				{
					cTextureTile *pTile = pTexture->m_pTextureTiles[index];
					int16_t x = sX;
					int16_t y = sY;
					// todo: fast div
					int16_t row = (int)(index / (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE));
					int16_t column = index % (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE);

					x += column * TEXTURE_GRID_SIZE;
					y += row * TEXTURE_GRID_SIZE;

					switch (m_szDrawMode)
					{
						case DirectOpaque:
							pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
						break;
						case DirectTransparent:
							pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
						break;
					}
		
					// Hack to ensure 2D & 3D objects are layered correctly
					float sceneZ = ((fOpacity < 1.0f) ? (float(layer) + 1.99f) * LAYER_SCALE : (layer + 1) * (layer == LayerBg ? 1.f : LAYER_SCALE));

					pvr_dr_state_t state;
					pvr_vertex_t *vert;
					pvr_dr_init(state);

					// top left 0,0
					vert = pvr_dr_target(state);
					vert->flags = PVR_CMD_VERTEX;
					vert->x = sX;
					vert->y = sY;
					vert->z = sceneZ;
					vert->argb = colour;
					vert->u = 0;
					vert->v = 0;
					pvr_dr_commit(vert);

					// top right
					vert = pvr_dr_target(state);
					vert->flags = PVR_CMD_VERTEX;
					vert->x = sX + sW;
					vert->y = sY;
					vert->z = sceneZ;
					vert->argb = colour;
					vert->u = 1.f;
					vert->v = 0.f;
					pvr_dr_commit(vert);

					// bottom left
					vert = pvr_dr_target(state);
					vert->flags = PVR_CMD_VERTEX;
					vert->x = sX;
					vert->y = sY + sH;
					vert->z = sceneZ;
					vert->argb = colour;
					vert->u = 0.f;
					vert->v = 1.f;
					pvr_dr_commit(vert);

					// bottom right
					vert = pvr_dr_target(state);
					vert->flags = PVR_CMD_VERTEX_EOL;
					vert->x = sX + sW;
					vert->y = sY + sH;
					vert->z = sceneZ;
					vert->argb = colour;
					vert->u = 1.f;
					vert->v = 1.f;
					pvr_dr_commit(vert);

					m_lNumVerts += 4;
				}
			}
		}

		inline __attribute__((always_inline)) void DrawLine2DDirect(uint32_t colour, float x1, float y1, float x2, float y2, uint16_t layer, float opacity, uint8_t thickness)
		{
			switch (m_szDrawMode)
			{
				case DirectOpaque:		
					pvr_prim(&m_hdrFlatOpCol, sizeof(m_hdrFlatOpCol));
				break;
				case DirectTransparent:			
					pvr_prim(&m_hdrFlatTrCol, sizeof(m_hdrFlatTrCol));
				break;
			}
			
			// Hack to ensure 2D & 3D objects are layered correctly
			float sceneZ = ((opacity < 1.0f) ? (float(layer) + 1.99f) * LAYER_SCALE : (layer + 1) * LAYER_SCALE);

		
			pvr_dr_state_t state;
			pvr_vertex_t *vert;
			pvr_dr_init(state);

			// top left 0,0
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = x1;
			vert->y = y1;
			vert->z = sceneZ;
			vert->argb = colour;
			pvr_dr_commit(vert);

			// top right
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = x2;
			vert->y = y2;
			vert->z = sceneZ;
			vert->argb = colour;
			pvr_dr_commit(vert);

			// bottom left
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = x1 + thickness;
			vert->y = y1;
			vert->z = sceneZ;
			vert->argb = colour;
			pvr_dr_commit(vert);

			// bottom right
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX_EOL;
			vert->x = x2 + thickness;	
			vert->y = y2;
			vert->z = sceneZ;
			vert->argb = colour;
			pvr_dr_commit(vert);

			m_lNumVerts += 4;	
		}
	
		/// <summary>
		/// Draws a textured quad, scaled to the given width & height.
		/// </summary>
		inline __attribute__((always_inline)) void DrawQuadDeferred(cTexture *pTexture, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer, float opacity, uint32_t ulColour = 0xFFFFFFFF)
		{
			uint16_t tileCount = pTexture->m_sNumTiles;

			if (tileCount == 1)
			{
				cTextureTile *pTile = pTexture->m_pTextureTiles[0];
				// todo: clean up | fast div?
				float adjustedMaxU = (1.0 / pTile->m_szTileWidth) * pTile->m_szImageWidth;
				float adjustedMaxV = (1.0 / pTile->m_szTileHeight) * pTile->m_szImageHeight;
				cBatchedQuad *quad = new cBatchedQuad(
					sX,	sY,
					sW,	sH,
					pTile->m_pParentTexture, pTile->m_szId,
					0.f, 0.f, 
					adjustedMaxU, adjustedMaxV,
					ulColour, layer + 1);
				
				cDisplayListQueue *queue = GetQueue(layer, opacity);
				queue->Add(quad);				
			}
			else if (tileCount > 1)
			{
				float scaleX = rspFastDiv(float(sW), pTexture->m_szImageWidth);
				float scaleY = rspFastDiv(float(sH), pTexture->m_szImageHeight);

				for (int index = 0; index < tileCount; index++)
				{
					cTextureTile *pTile = pTexture->m_pTextureTiles[index];
					int16_t x = sX;
					int16_t y = sY;
					// todo: fast div?
					int16_t row = (int)(index / (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE));
					int16_t column = index % (pTexture->m_szTextureWidth / TEXTURE_GRID_SIZE);
										
					x += (column * TEXTURE_GRID_SIZE) * scaleX;
					y += (row * TEXTURE_GRID_SIZE) * scaleY;
										
					float adjustedMaxU = rspFastDiv(1.0, pTile->m_szTileWidth) * pTile->m_szImageWidth;
					float adjustedMaxV = rspFastDiv(1.0 , pTile->m_szTileHeight) * pTile->m_szImageHeight;

					cBatchedQuad *quad = new cBatchedQuad(
						x,	y,
						(pTile->m_szImageWidth * scaleX), (pTile->m_szImageHeight * scaleY),
						pTile->m_pParentTexture, pTile->m_szId,
						0.f, 0.f, 
						adjustedMaxU, adjustedMaxV,
						ulColour, layer + 1);
					
					cDisplayListQueue *queue = GetQueue(layer, opacity);

					queue->Add(quad);
				}
			}
		}

		inline __attribute__((always_inline)) void DrawQuadDeferred(cTexture *pTexture, uint8_t ucTileId, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer, float opacity, uint32_t ulColour = 0xFFFFFFFF)
		{					
			cTextureTile *pTile = pTexture->m_pTextureTiles[ucTileId];
									
			cBatchedQuad *quad = new cBatchedQuad(
				sX,	sY,
				sW, sH,
				pTexture, ucTileId,
				fStartU, fStartV,
				fEndU, fEndV,
				ulColour, layer + 1);
			
			cDisplayListQueue *queue = GetQueue(layer, opacity);

			queue->Add(quad);											
		}
		
	inline __attribute__((always_inline)) void DrawPartialTiledQuadDeferred(cTexture *pTexture, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t layer, float opacity, uint32_t ulColour = 0xFFFFFFFF)		
	{			
	// todo: This is far from general purpose...			
	float fMaxCellU = rspFastDiv(1.f, float(pTexture->m_szTextureWidth)) * float(TEXTURE_GRID_SIZE);
	float fMaxCellV = rspFastDiv(1.f, float(pTexture->m_szTextureHeight)) * float(TEXTURE_GRID_SIZE);
																				
	int16_t sTileCols = int16_t(rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE)));
	int16_t sTileRows = int16_t(rspSlowDiv(float(pTexture->m_szTextureHeight), float(TEXTURE_GRID_SIZE)));

	float startx = fStartU * pTexture->m_szTextureWidth;
	float starty = fStartV * pTexture->m_szTextureHeight;
	float endx = fEndU * pTexture->m_szTextureWidth;
	float endy = fEndV * pTexture->m_szTextureHeight;
						
	int16_t sTileColStart = (fStartU > 0.f) ? int16_t( rspSlowDiv( fStartU, fMaxCellU)) : 0;
	int16_t sTileRowStart = (fStartV > 0.f) ? int16_t( rspSlowDiv( fStartV, fMaxCellV)) : 0;			
	int16_t sTileColEnd = int16_t(rspSlowDiv(fEndU, fMaxCellU));
	int16_t sTileRowEnd = int16_t(rspSlowDiv(fEndV, fMaxCellV));
			
	if (fStartV > fMaxCellV)
	{
		fStartV -= fMaxCellV;
		fStartV *= float(sTileRows);
		fEndV -= fMaxCellV;
		fEndV *= float(sTileRows);				
	}
			
	float fRemainingU = fEndU - fStartU;
	float fRemainingV = fEndV - fStartV;

	float startU = fStartU;
	float startV = fStartV;
									
	int16_t sX1 = sX;
	int16_t sY1 = sY;

	// todo: greasy, greasy hack, but ran out of time - Needs to be fixed if to be reusable
	float scaleX = 0.79f;
	float scaleY = 0.79f;
	//float scaleX = rspFastDiv(float(sW), (fRemainingU * sTileCols) * (TEXTURE_GRID_SIZE * sTileCols));
	//float scaleY = rspFastDiv(float(sH), (fRemainingV * sTileRows) * (TEXTURE_GRID_SIZE * sTileRows));

	for (int16_t index = (sTileRowStart * sTileCols) + sTileColStart; index <= (sTileRowEnd * sTileCols) + sTileColEnd; index++)					
	{		
		cTextureTile *pTile = pTexture->m_pTextureTiles[index];
		float endU;
		float endV;			
		int16_t row = int16_t(rspSlowDiv(float(index), rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE))));
		int16_t column = index % int16_t(rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE)));
		if (column == 0)
		{
			fRemainingU = fEndU - fStartU;
			startU = fStartU;
			sX = sX1;
		}
						
		if (fRemainingU > fMaxCellU)
		{
			fRemainingU -= fMaxCellU * (column+1);
			endU = 1.f; // Full tile width				
		}
		else
		{
			endU = fRemainingU * (column+1);
		}

		if (fRemainingV > fMaxCellV)
		{
			fRemainingV -= fMaxCellV * (row);
		}

		if (fRemainingV > fMaxCellV) // Full-height cell
		{
			endV = 1.f; // Full tile height									
		}
		else if (row == sTileRows -1 && row == sTileRowStart)
		{
			endV = (fEndV-fMaxCellV) * 2.f;
		}
		else
		{
			endV = startV + (fRemainingV * (row+1));
		}
																																					
		float fCellWidth =  (endU * TEXTURE_GRID_SIZE) * scaleX;
		float fCellHeight = (endV * TEXTURE_GRID_SIZE) * scaleY;
			
		cBatchedQuad *quad = new cBatchedQuad(
			sX,	sY,
			uint16_t(fCellWidth), uint16_t(fCellHeight),
			pTexture, index,
			startU, startV,
			endU, endV,
			ulColour, layer + 1);

		cDisplayListQueue *queue = GetQueue(layer, opacity);
		queue->Add(quad);	
				
		sX += int16_t(fCellWidth);

		if (column == sTileCols -1)
		sY += int16_t((fCellHeight));
	}				
}


	inline __attribute__((always_inline)) void DrawPartialTiledQuadDirect(cTexture *pTexture, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t sLayer, float fOpacity, uint32_t ulColour = 0xFFFFFFFF)		
	{			
	// todo: This is far from general purpose...			
	float fMaxCellU = rspFastDiv(1.f, float(pTexture->m_szTextureWidth)) * float(TEXTURE_GRID_SIZE);
	float fMaxCellV = rspFastDiv(1.f, float(pTexture->m_szTextureHeight)) * float(TEXTURE_GRID_SIZE);
																				
	int16_t sTileCols = int16_t(rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE)));
	int16_t sTileRows = int16_t(rspSlowDiv(float(pTexture->m_szTextureHeight), float(TEXTURE_GRID_SIZE)));

	float startx = fStartU * pTexture->m_szTextureWidth;
	float starty = fStartV * pTexture->m_szTextureHeight;
	float endx = fEndU * pTexture->m_szTextureWidth;
	float endy = fEndV * pTexture->m_szTextureHeight;
						
	int16_t sTileColStart = (fStartU > 0.f) ? int16_t( rspSlowDiv( fStartU, fMaxCellU)) : 0;
	int16_t sTileRowStart = (fStartV > 0.f) ? int16_t( rspSlowDiv( fStartV, fMaxCellV)) : 0;			
	int16_t sTileColEnd = int16_t(rspSlowDiv(fEndU, fMaxCellU));
	int16_t sTileRowEnd = int16_t(rspSlowDiv(fEndV, fMaxCellV));
			
	if (fStartV > fMaxCellV)
	{
		fStartV -= fMaxCellV;
		fStartV *= float(sTileRows);
		fEndV -= fMaxCellV;
		fEndV *= float(sTileRows);				
	}
			
	float fRemainingU = fEndU - fStartU;
	float fRemainingV = fEndV - fStartV;

	float startU = fStartU;
	float startV = fStartV;
									
	int16_t sX1 = sX;
	int16_t sY1 = sY;

	// todo: greasy, greasy bodge
	float scaleX = 0.79f;
	float scaleY = 0.79f;
	//float scaleX = rspFastDiv(float(sW), (fRemainingU * sTileCols) * (TEXTURE_GRID_SIZE * sTileCols));
	//float scaleY = rspFastDiv(float(sH), (fRemainingV * sTileRows) * (TEXTURE_GRID_SIZE * sTileRows));

	// Hack to ensure 2D & 3D objects are layered correctly
	float sceneZ = ((fOpacity < 1.0f) ? (float(sLayer) + 1.99f) * LAYER_SCALE : (sLayer + 1) * (sLayer == LayerBg ? 1.f : LAYER_SCALE));
	int32_t lNumVerts = 0;
	for (int16_t index = (sTileRowStart * sTileCols) + sTileColStart; index <= (sTileRowEnd * sTileCols) + sTileColEnd; index++)					
	{								
		cTextureTile *pTile = pTexture->m_pTextureTiles[index];
	
		switch (m_szDrawMode)
		{
			case DirectOpaque:
				pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
				break;
			case DirectTransparent:
				pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
				break;
		}

		pvr_dr_state_t state;
		pvr_vertex_t *vert;
		pvr_dr_init(state);

		float endU;		
		float endV;			
		int16_t row = int16_t(rspSlowDiv(float(index), rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE))));
		int16_t column = index % int16_t(rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE)));
		if (column == 0)
		{
			fRemainingU = fEndU - fStartU;
			startU = fStartU;
			sX = sX1;
		}
						
		if (fRemainingU > fMaxCellU)
		{
			fRemainingU -= fMaxCellU * (column+1);
			endU = 1.f; // Full tile width				
		}
		else
		{
			endU = fRemainingU * (column+1);
		}

		if (fRemainingV > fMaxCellV)
		{
			fRemainingV -= fMaxCellV * (row);
		}

		if (fRemainingV > fMaxCellV) // Full-height cell
		{
			endV = 1.f; // Full tile height									
		}
		else if (row == sTileRows -1 && row == sTileRowStart)
		{
			endV = (fEndV-fMaxCellV) * 2.f;
		}
		else
		{
			endV = startV + (fRemainingV * (row+1));
		}
																																					
		float fCellWidth =  (endU * TEXTURE_GRID_SIZE) * scaleX;
		float fCellHeight = (endV * TEXTURE_GRID_SIZE) * scaleY;
															
		// top left 0,0
		vert = pvr_dr_target(state);
		vert->flags = PVR_CMD_VERTEX;
		vert->x = sX;
		vert->y = sY;
		vert->z = sceneZ;
		vert->argb = ulColour;
		vert->u = startU;
		vert->v = startV;
		pvr_dr_commit(vert);

		// top right
		vert = pvr_dr_target(state);
		vert->flags = PVR_CMD_VERTEX;
		vert->x = sX + fCellWidth;
		vert->y = sY;
		vert->z = sceneZ;
		vert->argb = ulColour;
		vert->u = endU;
		vert->v = startV;
		pvr_dr_commit(vert);

		// bottom left
		vert = pvr_dr_target(state);
		vert->flags = PVR_CMD_VERTEX;
		vert->x = sX;
		vert->y = sY + fCellHeight;
		vert->z = sceneZ;
		vert->argb = ulColour;
		vert->u = startU;
		vert->v = endV;
		pvr_dr_commit(vert);

		// bottom right
		vert = pvr_dr_target(state);
		vert->flags = PVR_CMD_VERTEX_EOL;
		vert->x = sX + fCellWidth;
		vert->y = sY + fCellHeight;
		vert->z = sceneZ;
		vert->argb = ulColour;
		vert->u = endU;
		vert->v = endV;
		pvr_dr_commit(vert);
				
		lNumVerts += 4;

		sX += int16_t(fCellWidth);

		if (column == sTileCols -1)
		sY += int16_t((fCellHeight));
	}	

	m_lNumVerts += lNumVerts;			
}



		// Draws a quad using multiple tiles.
		inline __attribute__((always_inline)) void DrawTiledQuadDirect(cTexture *pTexture, int16_t sX, int16_t sY, uint16_t sLayer, float fOpacity = 1.f, uint32_t colour = 0xFFFFFFFF)
		{
			int32_t lNumVerts = 0;
			for (int index = 0; index < pTexture->m_sNumTiles; index++)
			{
				cTextureTile *pTile = pTexture->m_pTextureTiles[index];
				if (pTile->m_bIsVQ)
				{					
					float div = rspSlowDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE));
					int16_t row = int16_t(rspSlowDiv(float(index), div));
					int16_t column = index % int16_t(div);
				
					float x = sX + (column * TEXTURE_GRID_SIZE);
					float y = sY + (row * TEXTURE_GRID_SIZE);

					uint16_t tileWidth = pTile->m_szTileWidth;
					uint16_t tileHeight = pTile->m_szTileHeight;
					uint16_t imageWidth = pTile->m_szImageWidth;
					uint16_t imageHeight = pTile->m_szImageHeight;

					float adjustedMaxU = rspFastDiv(1.0f, float(tileWidth)) * imageWidth;
					float adjustedMaxV = rspFastDiv(1.0f, float(tileHeight)) * imageHeight;

					int x2 = x + tileWidth;
					int y2 = y + tileHeight;
					
					// todo: optimise?
					bool inClip = ((
						(x >= m_sSceneClipX || x2 >= m_sSceneClipX) &&
						(x <= m_sSceneClipX + m_sSceneClipW || x2 <= m_sSceneClipX + m_sSceneClipW)) &&
						((y >= m_sSceneClipY || y2 >= m_sSceneClipY) &&
						(y <= m_sSceneClipY + m_sSceneClipH || y2 <= m_sSceneClipY + m_sSceneClipH))
					);

					if (inClip)
					{

						switch (m_szDrawMode)
						{
							case DirectOpaque:
								pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
							break;
							case DirectTransparent:
								pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
							break;
						}
		
						// Hack to ensure 2D & 3D objects are layered correctly
						float sceneZ = ((fOpacity < 1.0f) ? (float(sLayer) + 1.99f) * LAYER_SCALE : (sLayer + 1) * (sLayer == LayerBg ? 1.f : LAYER_SCALE));

						pvr_dr_state_t state;
						pvr_vertex_t *vert;
						pvr_dr_init(state);

						// top left 0,0
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x;
						vert->y = y;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 0;
						vert->v = 0;
						pvr_dr_commit(vert);

						// top right
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x + imageWidth;
						vert->y = y;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 1.f;
						vert->v = 0.f;
						pvr_dr_commit(vert);

						// bottom left
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x;
						vert->y = y + imageHeight;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 0.f;
						vert->v = 1.f;
						pvr_dr_commit(vert);

						// bottom right
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX_EOL;
						vert->x = x + imageWidth;
						vert->y = y + imageHeight;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 1.f;
						vert->v = 1.f;
						pvr_dr_commit(vert);

						lNumVerts += 4;
					}
				}
			}
			m_lNumVerts += lNumVerts;
		}

		// Draws a quad using multiple tiles.
		inline __attribute__((always_inline)) void DrawTiledQuadDirectScaled(cTexture *pTexture, int16_t sX, int16_t sY, int16_t sW, int16_t sH, uint16_t sLayer, float fOpacity = 1.f, uint32_t colour = 0xFFFFFFFF)
		{
			float scaleX = rspFastDiv(float(sW), pTexture->m_szImageWidth);
			float scaleY = rspFastDiv(float(sH), pTexture->m_szImageHeight);
			int32_t lNumVerts = 0;

			for (int index = 0; index < pTexture->m_sNumTiles; index++)
			{
				cTextureTile *pTile = pTexture->m_pTextureTiles[index];
				if (pTile->m_bIsVQ)
				{
					
					float adjustedMaxU = rspFastDiv(1.0f, float(pTile->m_szTileWidth)) * pTile->m_szImageWidth;
					float adjustedMaxV = rspFastDiv(1.0f, float(pTile->m_szTileHeight)) * pTile->m_szImageHeight;
									
					float div = rspFastDiv(float(pTexture->m_szTextureWidth), float(TEXTURE_GRID_SIZE));					
					int16_t row = int16_t(rspSlowDiv(float(index), div));
					int16_t column = index % int16_t(div);
					
					uint16_t imageWidth = pTile->m_szImageWidth * scaleX;
					uint16_t imageHeight = pTile->m_szImageHeight * scaleY;

					float x = sX + ((column * TEXTURE_GRID_SIZE) * scaleX);
					float y = sY + ((row * TEXTURE_GRID_SIZE) * scaleY);
					float x2 = x + float(pTile->m_szTileWidth);
					float y2 = y + float(pTile->m_szTileHeight);
										
					// todo: optimise?
					bool inClip = ((
						(x >= m_sSceneClipX || x2 >= m_sSceneClipX) &&
						(x <= m_sSceneClipX + m_sSceneClipW || x2 <= m_sSceneClipX + m_sSceneClipW)) &&
						((y >= m_sSceneClipY || y2 >= m_sSceneClipY) &&
						(y <= m_sSceneClipY + m_sSceneClipH || y2 <= m_sSceneClipY + m_sSceneClipH))
					);

					if (inClip)
					{
						switch (m_szDrawMode)
						{
							case DirectOpaque:
								pvr_prim(&pTile->hdrOP, sizeof(pTile->hdrOP));
							break;
							case DirectTransparent:
								pvr_prim(&pTile->hdrTR, sizeof(pTile->hdrTR));
							break;
						}
		
						// Hack to ensure 2D & 3D objects are layered correctly
						float sceneZ = ((fOpacity < 1.0f) ? (float(sLayer) + 1.99f) * LAYER_SCALE : (sLayer + 1) * (sLayer == LayerBg ? 1.f : LAYER_SCALE));

						pvr_dr_state_t state;
						pvr_vertex_t *vert;
						pvr_dr_init(state);

						// top left 0,0
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x;
						vert->y = y;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 0;
						vert->v = 0;
						pvr_dr_commit(vert);

						// top right
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x + imageWidth;
						vert->y = y;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 1.f;
						vert->v = 0.f;
						pvr_dr_commit(vert);

						// bottom left
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX;
						vert->x = x;
						vert->y = y + imageHeight;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 0.f;
						vert->v = 1.f;
						pvr_dr_commit(vert);

						// bottom right
						vert = pvr_dr_target(state);
						vert->flags = PVR_CMD_VERTEX_EOL;
						vert->x = x + imageWidth;
						vert->y = y + imageHeight;
						vert->z = sceneZ;
						vert->argb = colour;
						vert->u = 1.f;
						vert->v = 1.f;
						pvr_dr_commit(vert);

						lNumVerts += 4;
					}
				}
			}

			m_lNumVerts += lNumVerts;
		}

		/// <summary>
		/// toto
		/// </summary>
		inline __attribute__((always_inline)) void DrawTileDirectPrePrim(cTextureTile *, pvr_dr_state_t *state, float fStartU, float fStartV, float fEndU, float fEndV, int16_t sX, int16_t sY, uint16_t sW, uint16_t sH, int16_t sLayer, float fOpacity = 1.f, uint32_t colour = 0xFFFFFFFF)
		{						
			// Hack to ensure 2D & 3D objects are layered correctly
			float sceneZ = 0.f;

			if (fOpacity < 1.0f)
			{
				if (sLayer != LayerBg)
				{
					sceneZ = (float(sLayer) + 1.99f) * LAYER_SCALE;
				}
				else
				{
					sceneZ = (sLayer + 1) * LAYER_SCALE;
				}
			}
			else
			{
				sceneZ = (sLayer + 1) * LAYER_SCALE;
			}
			
			pvr_vertex_t *vert;
			
			// top left 0,0
			vert = pvr_dr_target(*state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX;
			vert->y = sY;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fStartU;
			vert->v = fStartV;
			pvr_dr_commit(vert);

			// top right
			vert = pvr_dr_target(*state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX + sW;
			vert->y = sY;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fEndU;
			vert->v = fStartV;
			pvr_dr_commit(vert);

			// bottom left
			vert = pvr_dr_target(*state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = sX;
			vert->y = sY + sH;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fStartU;
			vert->v = fEndV;
			pvr_dr_commit(vert);

			// bottom right
			vert = pvr_dr_target(*state);
			vert->flags = PVR_CMD_VERTEX_EOL;
			vert->x = sX + sW;
			vert->y = sY + sH;
			vert->z = (sLayer + 1) * LAYER_SCALE;
			vert->z = sceneZ;
			vert->argb = colour;
			vert->u = fEndU;
			vert->v = fEndV;
			pvr_dr_commit(vert);

			m_lNumVerts += 4;
		}

		// Draw texture tiles.
		void DrawTile(cTextureTile *, int16_t, int16_t, int16_t, float = 1.0f, float = 1.0f, float = 1.0f, float = 1.0f);
		void DrawTileScaled(cTextureTile *, int16_t, int16_t, int16_t, int16_t, int16_t, float = 1.0f, float = 1.0f, float = 1.0f, float = 1.0f);

		// Draws a quad using multiple tiles.
		void DrawTiledQuad(cTexture *, int16_t, int16_t, RRect *, float = 1.0f, float = 1.0f, float = 1.0f, float = 1.0f);

		// Draws a shaded triangle - Only used in direct draw modes.
		void DrawTriShaded(uint32_t, uint32_t, uint32_t, RP3d *, RP3d *, RP3d *, int16_t);

		// Draws a flat-coloured 2D line - Only used in direct draw modes.
		void DrawLine2D(uint32_t, float, float, float, float, uint16_t, float = 1.0f, uint8_t = 1);

		// Draws a coloured translucent quad.
		void DrawDebugQuad(uint16_t, int16_t, int16_t, uint16_t, uint16_t, int16_t = 0);

		// Draws text strings to the screen.
		void DrawGuiText();

		// Crops the screen with a "letterbox" effect overlay.
		void DrawLetterBoxOverlay(uint8_t = 0, uint8_t = 0, uint8_t = 0, uint8_t = 0, int8_t = 0);

		// Crops the screen with a "letterbox" effect overlay.
		inline __attribute__((always_inline)) void DrawLetterBoxOverlayDirect(uint8_t top = 0, uint8_t bottom = 0, uint8_t left = 0, uint8_t right = 0, int8_t cYoffset = 0)
		{
			// Left
			DrawQuadDirect(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), int16_t(0), int16_t(0), left, int16_t(480), cRenderMgr::LayerHud);

			// Right
			DrawQuadDirect(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), int16_t(640 - right), int16_t(0), int16_t(640 - right), int16_t(480), cRenderMgr::LayerHud);

			// top
			DrawQuadDirect(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), left, int16_t(0), int16_t(640 - right), top, cRenderMgr::LayerHud);

			// bottom
			DrawQuadDirect(uint32_t(PACK_ARGB8888(255, 0, 0, 0)), left, int16_t(480 - bottom), int16_t(640 - right), int16_t(480), cRenderMgr::LayerHud);
		}

		// Beging/End scene & direct draw modes.
		void BeginDirectDrawMode(int8_t);
		void EndDirectDrawMode();
		bool BeginScene(bool = false);
		void EndScene(void);
	};
}

#endif //PVR_H
