#define CC_DYNAMIC_VBS_ARE_STATIC
#include "../_GraphicsBase.h"
#include "../Errors.h"
#include "../Logger.h"
#include "../Window.h"

#include <malloc.h>
#include <rsx/rsx.h>
#include <sysutil/video.h>
#include "rsx_gpu.h"

static gcmContextData* context;
static u32 cur_fb;


/*########################################################################################################################*
*----------------------------------------------------- Vertex Shaders ----------------------------------------------------*
*#########################################################################################################################*/
typedef struct CCVertexProgram {
	rsxVertexProgram* prog;
	void* ucode;
	rsxProgramConst* mvp;
	rsxProgramConst* uv_offset;
} VertexProgram;

extern const u8 vs_coloured_vpo[];
extern const u8 vs_textured_vpo[];
extern const u8 vs_offset_vpo[];

static VertexProgram  VP_list[3];
static VertexProgram* VP_active;

static cc_bool textureOffseting;
static float textureOffset[4] CC_ALIGNED(16);
static struct Matrix mvp      CC_ALIGNED(64);


static void VP_Load(VertexProgram* vp, const u8* source) {
	vp->prog = (rsxVertexProgram*)source;
	u32 size = 0;
	rsxVertexProgramGetUCode(vp->prog, &vp->ucode, &size);
	
	vp->mvp       = rsxVertexProgramGetConst(vp->prog, "mvp");
	vp->uv_offset = rsxVertexProgramGetConst(vp->prog, "uv_offset");
}

static void LoadVertexPrograms(void) {
	VP_Load(&VP_list[0], vs_coloured_vpo);
	VP_Load(&VP_list[1], vs_textured_vpo);
	VP_Load(&VP_list[2], vs_offset_vpo);
}

static void VP_SwitchActive(void) {
	int index = gfx_format == VERTEX_FORMAT_TEXTURED ? 1 : 0;
	if (textureOffseting) index = 2;
	
	VertexProgram* VP = &VP_list[index];
	if (VP == VP_active) return;
	VP_active = VP;
	
	rsxLoadVertexProgram(context, VP->prog, VP->ucode);
}

static void VP_UpdateUniforms() {
	// TODO: dirty uniforms instead
	for (int i = 0; i < Array_Elems(VP_list); i++)
	{
		VertexProgram* vp = &VP_list[i];
		rsxSetVertexProgramParameter(context, vp->prog, vp->mvp, (float*)&mvp);
	}
	
	if (VP_active == &VP_list[2]) {
		VertexProgram* vp = &VP_list[2];
		rsxSetVertexProgramParameter(context, vp->prog, vp->uv_offset, textureOffset);
	}
}


/*########################################################################################################################*
*---------------------------------------------------- Fragment Shaders ---------------------------------------------------*
*#########################################################################################################################*/
typedef struct CCFragmentProgram {
	rsxFragmentProgram* prog;
	void* ucode;
	u32* buffer;
	u32 offset;
} FragmentProgram;

extern const u8 ps_textured_fpo[];
extern const u8 ps_coloured_fpo[];

static FragmentProgram  FP_list[2];
static FragmentProgram* FP_active;


static void FP_Load(FragmentProgram* fp, const u8* source) {
	fp->prog = (rsxFragmentProgram*)source;
	u32 size = 0;
	rsxFragmentProgramGetUCode(fp->prog, &fp->ucode, &size);
	
	fp->buffer = (u32*)rsxMemalign(128, size);
	Mem_Copy(fp->buffer, fp->ucode, size);
	gcmAddressToOffset(fp->buffer, &fp->offset);
}

static void LoadFragmentPrograms(void) {
	FP_Load(&FP_list[0], ps_coloured_fpo);
	FP_Load(&FP_list[1], ps_textured_fpo);
}

static void FP_SwitchActive(void) {
	int index = gfx_format == VERTEX_FORMAT_TEXTURED ? 1 : 0;

	FragmentProgram* FP = &FP_list[index];
	if (FP == FP_active) return;
	FP_active = FP;
	
	rsxLoadFragmentProgramLocation(context, FP->prog, FP->offset, GCM_LOCATION_RSX);
}


/*########################################################################################################################*
*---------------------------------------------------------- Setup---------------------------------------------------------*
*#########################################################################################################################*/
static u32  color_pitch;
static u32  color_offset[2];
static u32* color_buffer[2];

static u32  depth_pitch;
static u32  depth_offset;
static u32* depth_buffer;

#define GCM_LABEL_INDEX 255
static u32 labelID = 1;

#define CB_SIZE   (1024 * 1024) // TODO: smaller command buffer?
#define HOST_SIZE (32 * 1024 * 1024)

#define uint_to_ptr(raw)    ((void*)((uintptr_t)(raw)))
#define ptr_to_uint(raw) ((uint32_t)((uintptr_t)(raw)))
extern s32 gcmInitBodyEx(uint32_t ctx, uint32_t cbSize, uint32_t ioSize, uint32_t ioBuf);

static void CreateContext(void) {
	void* host_buf = memalign(1024 * 1024, HOST_SIZE);

	// PS3 pointers are 32 bits in size, but our pointers are 64 bits
	uint32_t ctx_ptr = 0;
	int res = gcmInitBodyEx(ptr_to_uint(&ctx_ptr), CB_SIZE, HOST_SIZE, ptr_to_uint(host_buf));
	if (res) Process_Abort2(res, "gcmInitBody failed");

	context = uint_to_ptr(ctx_ptr);
	rsxHeapInit();
}

static void WaitRSXFinish(void) {
	rsxSetWriteBackendLabel(context, GCM_LABEL_INDEX, labelID);
	rsxFlushBuffer(context);

	while (*(vu32*)gcmGetLabelAddress(GCM_LABEL_INDEX) != labelID)
		usleep(30);

	labelID++;
}

static void WaitRSXIdle(void) {
	rsxSetWriteBackendLabel(context, GCM_LABEL_INDEX, labelID);
	rsxSetWaitLabel(context,         GCM_LABEL_INDEX, labelID);

	labelID++;
	WaitRSXFinish();
}

static void ConfigureVideo(void) {
    WaitRSXIdle();

	videoState state;
	videoGetState(0, 0, &state);

	videoConfiguration vconfig = { 0 };
	vconfig.resolution = state.displayMode.resolution;
	vconfig.format     = VIDEO_BUFFER_FORMAT_XRGB;
	vconfig.pitch      = DisplayInfo.Width * sizeof(u32);
	
	videoConfigure(0, &vconfig, NULL, 0);
}

static void SetupBlendingState(void) {
	rsxSetBlendFunc(context, GCM_SRC_ALPHA, GCM_ONE_MINUS_SRC_ALPHA, GCM_SRC_ALPHA, GCM_ONE_MINUS_SRC_ALPHA);
	rsxSetBlendEquation(context, GCM_FUNC_ADD, GCM_FUNC_ADD);
}

static void AllocColorSurface(u32 i) {
	color_pitch     = DisplayInfo.Width * 4;
	color_buffer[i] = (u32*)rsxMemalign(64, DisplayInfo.Height * color_pitch);
	
	gcmAddressToOffset(color_buffer[i], &color_offset[i]);
	gcmSetDisplayBuffer(i, color_offset[i], color_pitch,
		DisplayInfo.Width, DisplayInfo.Height);
}

static void AllocDepthSurface(void) {
	depth_pitch  = DisplayInfo.Width * 4;
	depth_buffer = (u32*)rsxMemalign(64, DisplayInfo.Height * depth_pitch);
	
	gcmAddressToOffset(depth_buffer, &depth_offset);
}


/*########################################################################################################################*
*---------------------------------------------------------General---------------------------------------------------------*
*#########################################################################################################################*/
void SetRenderTarget(u32 index) {
	gcmSurface sf;

	sf.colorFormat		= GCM_SURFACE_X8R8G8B8;
	sf.colorTarget		= GCM_SURFACE_TARGET_0;
	sf.colorLocation[0]	= GCM_LOCATION_RSX;
	sf.colorOffset[0]	= color_offset[index];
	sf.colorPitch[0]	= color_pitch;

	sf.colorLocation[1]	= GCM_LOCATION_RSX;
	sf.colorLocation[2]	= GCM_LOCATION_RSX;
	sf.colorLocation[3]	= GCM_LOCATION_RSX;
	sf.colorOffset[1]	= 0;
	sf.colorOffset[2]	= 0;
	sf.colorOffset[3]	= 0;
	sf.colorPitch[1]	= 64;
	sf.colorPitch[2]	= 64;
	sf.colorPitch[3]	= 64;

	sf.depthFormat		= GCM_SURFACE_ZETA_Z24S8;
	sf.depthLocation	= GCM_LOCATION_RSX;
	sf.depthOffset		= depth_offset;
	sf.depthPitch		= depth_pitch;

	sf.type		        = GCM_SURFACE_TYPE_LINEAR;
	sf.antiAlias		= GCM_SURFACE_CENTER_1;

	sf.width		= DisplayInfo.Width;
	sf.height		= DisplayInfo.Height;
	sf.x			= 0;
	sf.y			= 0;

	rsxSetSurface(context, &sf);
}

static void InitGfxContext(void) {
	Gfx.MaxTexWidth  = 1024;
	Gfx.MaxTexHeight = 1024;
	Gfx.Created      = true;
	
	// https://github.com/ps3dev/PSL1GHT/blob/master/ppu/include/rsx/rsx.h#L30
	CreateContext();
	ConfigureVideo();
	gcmSetFlipMode(GCM_FLIP_VSYNC);
	
	AllocColorSurface(0);
	AllocColorSurface(1);
	AllocDepthSurface();
	gcmResetFlipStatus();
	
	SetupBlendingState();
	SetRenderTarget(cur_fb);
	
	LoadVertexPrograms();
	LoadFragmentPrograms();
}
static GfxResourceID white_square;

void Gfx_Create(void) {
	// TODO rethink all this
	if (!Gfx.Created) InitGfxContext();
	
	Gfx_RestoreState();
	gfx_format = -1;
	Gfx.NonPowTwoTexturesSupport = GFX_NONPOW2_UPLOAD;
}

void Gfx_Free(void) { Gfx_FreeState(); }


cc_bool Gfx_TryRestoreContext(void) { return true; }
cc_bool Gfx_WarnIfNecessary(void)   { return false; }
cc_bool Gfx_GetUIOptions(struct MenuOptionsScreen* s) { return false; }

static void Gfx_RestoreState(void) {
	InitDefaultResources();
	
	// 1x1 dummy white texture
	struct Bitmap bmp;
	BitmapCol pixels[1] = { BITMAPCOLOR_WHITE };
	Bitmap_Init(bmp, 1, 1, pixels);
	white_square = Gfx_CreateTexture(&bmp, 0, false);
}

static void Gfx_FreeState(void) {
	FreeDefaultResources(); 
	Gfx_DeleteTexture(&white_square);
}


u32* Gfx_AllocImage(u32* offset, s32 w, s32 h) {
	u32* pixels = (u32*)rsxMemalign(64, w * h * 4);
	gcmAddressToOffset(pixels, offset);
	return pixels;
}

void Gfx_TransferImage(u32 offset, s32 w, s32 h) {
	rsxSetTransferImage(context, GCM_TRANSFER_LOCAL_TO_LOCAL,
		color_offset[cur_fb], color_pitch, 0, 0,
		offset,               w * 4,       0, 0, 
		w, h, 4);
}


/*########################################################################################################################*
*-----------------------------------------------------State management----------------------------------------------------*
*#########################################################################################################################*/
static cc_uint32 clearColor;

void Gfx_SetFaceCulling(cc_bool enabled) {
	RSX_set_cull_face(context, enabled);
}

static void SetAlphaBlend(cc_bool enabled) {
	RSX_set_alpha_blend(context, enabled);
}
void Gfx_SetAlphaArgBlend(cc_bool enabled) { }

void Gfx_ClearColor(PackedCol color) {
	cc_uint32 R = PackedCol_R(color);
	cc_uint32 G = PackedCol_G(color);
	cc_uint32 B = PackedCol_B(color);

	clearColor  = B | (G << 8) | (R << 16) | (0xFF << 24);
}

void Gfx_SetDepthWrite(cc_bool enabled) {
	RSX_set_depth_write(context, enabled);
}

void Gfx_SetDepthTest(cc_bool enabled) {
	RSX_set_depth_test(context, enabled);
}

static void SetAlphaTest(cc_bool enabled) {
	RSX_set_alpha_test(context, enabled);
}

static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
	RSX_set_color_write_mask(context, r, g, b, a);
}

void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
	cc_bool enabled = !depthOnly;
	SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], 
				  enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]);
}


/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
	// Same as Direct3D9
	// TODO: should it be like OpenGL? ???
	*matrix = Matrix_Identity;

	matrix->row1.x =  2.0f / width;
	matrix->row2.y = -2.0f / height;
	matrix->row3.z =  1.0f / (zNear - zFar);

	matrix->row4.x = -1.0f;
	matrix->row4.y =  1.0f;
	matrix->row4.z = zNear / (zNear - zFar);
}

static float Cotangent(float x) { return Math_CosF(x) / Math_SinF(x); }
void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
	float zNear = 0.1f;
	float c = Cotangent(0.5f * fov);

	// Same as Direct3D9
	// TODO: should it be like OpenGL? ???
	*matrix = Matrix_Identity;

	matrix->row1.x =  c / aspect;
	matrix->row2.y =  c;
	matrix->row3.z = zFar / (zNear - zFar);
	matrix->row3.w = -1.0f;
	matrix->row4.z = (zNear * zFar) / (zNear - zFar);
	matrix->row4.w =  0.0f;
}


/*########################################################################################################################*
*-----------------------------------------------------------Misc----------------------------------------------------------*
*#########################################################################################################################*/
cc_result Gfx_TakeScreenshot(struct Stream* output) {
	return ERR_NOT_SUPPORTED;
}

void Gfx_GetApiInfo(cc_string* info) {
	int pointerSize = sizeof(void*) * 8;

	String_Format1(info, "-- Using PS3 (%i bit) --\n", &pointerSize);
	PrintMaxTextureInfo(info);
}

void Gfx_SetVSync(cc_bool vsync) {
	gfx_vsync = vsync;
}

static void ResetFrameState(void) {
	// Seems these states aren't persisted across frames
	// TODO: Why is that
	rsxSetAlphaFunc(context, GCM_GREATER, 0.5f);
	rsxSetBlendFunc(context, GCM_SRC_ALPHA, GCM_ONE_MINUS_SRC_ALPHA, GCM_SRC_ALPHA, GCM_ONE_MINUS_SRC_ALPHA);
        rsxSetBlendEquation(context, GCM_FUNC_ADD, GCM_FUNC_ADD);
        
	rsxSetColorMaskMrt(context, 0);
	rsxSetClearColor(context, clearColor);
	rsxSetClearDepthStencil(context, 0xFFFFFFFF);

	RSX_set_depth_func(context, GCM_LEQUAL);	
	RSX_set_depth_write(context, true);
	RSX_set_depth_test(context,  true);
	
	rsxSetUserClipPlaneControl(context, GCM_USER_CLIP_PLANE_DISABLE,
						GCM_USER_CLIP_PLANE_DISABLE,
						GCM_USER_CLIP_PLANE_DISABLE,
						GCM_USER_CLIP_PLANE_DISABLE,
						GCM_USER_CLIP_PLANE_DISABLE,
						GCM_USER_CLIP_PLANE_DISABLE);
        
	// NOTE: Must be called each frame, otherwise renders upside down at 4x zoom
	Gfx_SetViewport(0, 0, Game.Width, Game.Height);
	Gfx_SetScissor (0, 0, Game.Width, Game.Height);
}

// https://github.com/ps3dev/PSL1GHT/blob/master/ppu/include/rsx/rsx.h#L30
static cc_bool everFlipped;
void Gfx_WaitFlip(void) {
	if (everFlipped) {
		while (gcmGetFlipStatus() != 0) usleep(200);
	}
	
	everFlipped = true;
	gcmResetFlipStatus();
}

void Gfx_BeginFrame(void) {
	Gfx_WaitFlip();
	ResetFrameState();
}

void Gfx_ClearBuffers(GfxBuffers buffers) {
	RSX_clear_buffers(context, buffers & GFX_BUFFER_COLOR, buffers & GFX_BUFFER_DEPTH);
}

void Gfx_EndFrame(void) {
	gcmSetFlip(context, cur_fb);
	rsxFlushBuffer(context);
	gcmSetWaitFlip(context);

	cur_fb ^= 1;
	SetRenderTarget(cur_fb);
}

void Gfx_OnWindowResize(void) {
	Gfx_SetViewport(0, 0, Game.Width, Game.Height);
	Gfx_SetScissor (0, 0, Game.Width, Game.Height);
}

void Gfx_SetViewport(int x, int y, int w, int h) {
	f32 scale[4], offset[4];
	f32 zmin = 0.0f;
	f32 zmax = 1.0f;
	
	scale[0]  = w *  0.5f;
	scale[1]  = h * -0.5f;
	scale[2]  = (zmax - zmin) * 0.5f;
	scale[3]  = 0.0f;
	offset[0] = x + w * 0.5f;
	offset[1] = y + h * 0.5f;
	offset[2] = (zmax + zmin) * 0.5f;
	offset[3] = 0.0f;

	rsxSetViewport(context, x, y, w, h, zmin, zmax, scale, offset);
}

void Gfx_SetScissor(int x, int y, int w, int h) {
	RSX_set_scissor_rect(context, x, y, w, h);
}


/*########################################################################################################################*
*-------------------------------------------------------Index buffers-----------------------------------------------------*
*#########################################################################################################################*/
static int vb_size;

GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
	return (void*)1;
}

void Gfx_BindIb(GfxResourceID ib) { }
void Gfx_DeleteIb(GfxResourceID* ib) { }


/*########################################################################################################################*
*------------------------------------------------------Vertex buffers-----------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
	return rsxMemalign(128, count * strideSizes[fmt]);
}

void Gfx_BindVb(GfxResourceID vb) { 
	u32 offset;
	gcmAddressToOffset(vb, &offset);
	
	if (gfx_format == VERTEX_FORMAT_TEXTURED) {
		rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_POS,    0, offset, 
			SIZEOF_VERTEX_TEXTURED, 3, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
		rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_COLOR0, 0, offset + 12, 
			SIZEOF_VERTEX_TEXTURED, 4, GCM_VERTEX_DATA_TYPE_U8,  GCM_LOCATION_RSX);
		rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_TEX0,   0, offset + 16,
			SIZEOF_VERTEX_TEXTURED, 2, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
	} else {
		rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_POS,    0, offset, 
			SIZEOF_VERTEX_COLOURED, 3, GCM_VERTEX_DATA_TYPE_F32, GCM_LOCATION_RSX);
		rsxBindVertexArrayAttrib(context, GCM_VERTEX_ATTRIB_COLOR0, 0, offset + 12, 
			SIZEOF_VERTEX_COLOURED, 4, GCM_VERTEX_DATA_TYPE_U8,  GCM_LOCATION_RSX);
	}
}

void Gfx_DeleteVb(GfxResourceID* vb) {
	GfxResourceID data = *vb;/* TODO */
	if (data) rsxFree(data);
	*vb = 0;
}

void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
	vb_size = count * strideSizes[fmt];
	return vb;
}

void Gfx_UnlockVb(GfxResourceID vb) {
	rsxInvalidateVertexCache(context); // TODO needed?
}


/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
typedef struct CCTexture_ {
	cc_uint32 width, height;
	cc_uint32 pad[(128 - 8)/4]; // TODO better way of aligning to 128 bytes
	cc_uint32 pixels[];
} CCTexture;

// See Graphics_Dreamcast.c for twiddling explanation
// (only difference is dreamcast is XY while xbox is YX)
static CC_INLINE void TwiddleCalcFactors(unsigned w, unsigned h, 
										unsigned* maskX, unsigned* maskY) {
	*maskX = 0;
	*maskY = 0;
	int shift = 0;

	for (; w > 1 || h > 1; w >>= 1, h >>= 1)
	{
		if (w > 1 && h > 1) {
			// Add interleaved X and Y bits
			*maskY += 0x02 << shift;
			*maskX += 0x01 << shift;
			shift  += 2;
		} else if (w > 1) {
			// Add a linear X bit
			*maskX += 0x01 << shift;
			shift  += 1;		
		} else if (h > 1) {
			// Add a linear Y bit
			*maskY += 0x01 << shift;
			shift  += 1;		
		}
	}
}

GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
	int dst_w = Math_NextPowOf2(bmp->width);
	int dst_h = Math_NextPowOf2(bmp->height);
	int size  = dst_w * dst_h * 4;

	CCTexture* tex = (CCTexture*)rsxMemalign(128, 128 + size);
	
	tex->width  = dst_w;
	tex->height = dst_h;
	cc_uint32* dst = tex->pixels;

	int src_w = bmp->width, src_h = bmp->height;
	unsigned maskX, maskY;
	unsigned X = 0, Y = 0;
	TwiddleCalcFactors(dst_w, dst_h, &maskX, &maskY);
	
	for (int y = 0; y < src_h; y++)
	{
		cc_uint32* src = bmp->scan0 + y * rowWidth;
		X = 0;
		
		for (int x = 0; x < src_w; x++, src++)
		{
			dst[X | Y] = *src;
			X = (X - maskX) & maskX;
		}
		Y = (Y - maskY) & maskY;
	}
	return tex;
}

void Gfx_BindTexture(GfxResourceID texId) {
	CCTexture* tex = (CCTexture*)texId;
	if (!tex) tex  = white_square; 
	/* TODO */
	
	u32 offset;
	gcmAddressToOffset(tex->pixels, &offset);
	gcmTexture texture;

	texture.format		= GCM_TEXTURE_FORMAT_A8R8G8B8 | GCM_TEXTURE_FORMAT_SWZ;
	texture.mipmap		= 1;
	texture.dimension	= GCM_TEXTURE_DIMS_2D;
	texture.cubemap	= GCM_FALSE;
	texture.remap		= 		  ((GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_B_SHIFT) |
						   (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_G_SHIFT) |
						   (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_R_SHIFT) |
						   (GCM_TEXTURE_REMAP_TYPE_REMAP << GCM_TEXTURE_REMAP_TYPE_A_SHIFT) |
						   (GCM_TEXTURE_REMAP_COLOR_B << GCM_TEXTURE_REMAP_COLOR_B_SHIFT) |
						   (GCM_TEXTURE_REMAP_COLOR_G << GCM_TEXTURE_REMAP_COLOR_G_SHIFT) |
						   (GCM_TEXTURE_REMAP_COLOR_R << GCM_TEXTURE_REMAP_COLOR_R_SHIFT) |
						   (GCM_TEXTURE_REMAP_COLOR_A << GCM_TEXTURE_REMAP_COLOR_A_SHIFT));
	texture.width		= tex->width;
	texture.height		= tex->height;
	texture.depth		= 1;
	texture.location	= GCM_LOCATION_RSX;
	texture.pitch		= tex->width * 4;
	texture.offset		= offset;
	
	rsxInvalidateTextureCache(context, GCM_INVALIDATE_TEXTURE); // TODO needed
	
	rsxLoadTexture(context,    0, &texture);
	rsxTextureControl(context, 0, GCM_TRUE, 0<<8, 12<<8, GCM_TEXTURE_MAX_ANISO_1);
	rsxTextureFilter(context, 0, 0, GCM_TEXTURE_NEAREST, GCM_TEXTURE_NEAREST,
		GCM_TEXTURE_CONVOLUTION_QUINCUNX);			
	rsxTextureWrapMode(context, 0, GCM_TEXTURE_REPEAT, GCM_TEXTURE_REPEAT, GCM_TEXTURE_REPEAT, 0, GCM_TEXTURE_ZFUNC_LESS, 0);
}

void Gfx_DeleteTexture(GfxResourceID* texId) {
	GfxResourceID data = *texId;
	if (data) rsxFree(data);
	*texId = NULL;
}

void Gfx_UpdateTexture(GfxResourceID texId, int originX, int originY, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
	CCTexture* tex = (CCTexture*)texId;	
	cc_uint32* dst = tex->pixels;
	
	int width = part->width, height = part->height;
	unsigned maskX, maskY;
	unsigned X = 0, Y = 0;
	TwiddleCalcFactors(tex->width, tex->height, &maskX, &maskY);

	// Calculate start twiddled X and Y values
	for (int x = 0; x < originX; x++) { X = (X - maskX) & maskX; }
	for (int y = 0; y < originY; y++) { Y = (Y - maskY) & maskY; }
	unsigned startX = X;
	
	for (int y = 0; y < height; y++)
	{
		cc_uint32* src = part->scan0 + rowWidth * y;
		X = startX;
		
		for (int x = 0; x < width; x++, src++)
		{
			dst[X | Y] = *src;
			X = (X - maskX) & maskX;
		}
		Y = (Y - maskY) & maskY;
	}
	
	rsxInvalidateTextureCache(context, GCM_INVALIDATE_TEXTURE);
	/* TODO */
}

void Gfx_EnableMipmaps(void)  { }
void Gfx_DisableMipmaps(void) { }


/*########################################################################################################################*
*-----------------------------------------------------State management----------------------------------------------------*
*#########################################################################################################################*/
void Gfx_SetFog(cc_bool enabled) {/* TODO */
}

void Gfx_SetFogCol(PackedCol color) {/* TODO */
}

void Gfx_SetFogDensity(float value) {/* TODO */
}

void Gfx_SetFogEnd(float value) {/* TODO */
}

void Gfx_SetFogMode(FogFunc func) {/* TODO */
}


/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
static struct Matrix _view, _proj;

void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
	struct Matrix* dst = type == MATRIX_PROJ ? &_proj : &_view;
	*dst = *matrix;

	Matrix_Mul(&mvp, &_view, &_proj);
	VP_UpdateUniforms();
}

void Gfx_LoadMVP(const struct Matrix* view, const struct Matrix* proj, struct Matrix* mvp) {
	Gfx_LoadMatrix(MATRIX_VIEW, view);
	Gfx_LoadMatrix(MATRIX_PROJ, proj);
	Matrix_Mul(mvp, view, proj);
}

void Gfx_EnableTextureOffset(float x, float y) {
	textureOffseting = true;
	textureOffset[0] = x;
	textureOffset[1] = y;
	
	VP_SwitchActive();
	VP_UpdateUniforms();
}

void Gfx_DisableTextureOffset(void) {
	textureOffseting = false;
	VP_SwitchActive();
}


/*########################################################################################################################*
*----------------------------------------------------------Drawing--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_SetVertexFormat(VertexFormat fmt) {
	if (fmt == gfx_format) return;
	gfx_format = fmt;
	gfx_stride = strideSizes[fmt];
	
	VP_SwitchActive();
	FP_SwitchActive();
}

void Gfx_DrawVb_Lines(int verticesCount) {
	rsxDrawVertexArray(context, GCM_TYPE_LINES, 0, verticesCount);
}

void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex, DrawHints hints) {
	rsxDrawVertexArray(context, GCM_TYPE_QUADS, startVertex, verticesCount);
}

void Gfx_DrawVb_IndexedTris(int verticesCount) {
	rsxDrawVertexArray(context, GCM_TYPE_QUADS, 0, verticesCount);
}

void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
	rsxDrawVertexArray(context, GCM_TYPE_QUADS, startVertex, verticesCount);
}
