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

#include <vitasdk.h>

// TODO track last frame used on
static cc_bool gfx_depthOnly;
static int frontBufferIndex, backBufferIndex;
// Inspired from
// https://github.com/xerpi/gxmfun/blob/master/source/main.c
// https://github.com/vitasdk/samples/blob/6337766482561cf28092d21082202c0f01e3542b/gxm/textured_cube/src/main.c

#define DISPLAY_WIDTH   960
#define DISPLAY_HEIGHT  544
#define DISPLAY_STRIDE 1024

#define NUM_DISPLAY_BUFFERS 3 // TODO: or just 2?
#define MAX_PENDING_SWAPS   (NUM_DISPLAY_BUFFERS - 1)

static void GPUBuffers_DeleteUnreferenced(void);
static void GPUTextures_DeleteUnreferenced(void);
static cc_uint32 frameCounter;
static cc_bool in_scene;

static SceGxmContext* gxm_context;

static SceUID vdm_ring_buffer_uid;
static void*  vdm_ring_buffer_addr;
static SceUID vertex_ring_buffer_uid;
static void*  vertex_ring_buffer_addr;
static SceUID fragment_ring_buffer_uid;
static void*  fragment_ring_buffer_addr;
static SceUID fragment_usse_ring_buffer_uid;
static void*  fragment_usse_ring_buffer_addr;
static unsigned int fragment_usse_offset;

static SceGxmRenderTarget* gxm_render_target;
static SceGxmColorSurface gxm_color_surfaces[NUM_DISPLAY_BUFFERS];
static SceUID gxm_color_surfaces_uid[NUM_DISPLAY_BUFFERS];
static void*  gxm_color_surfaces_addr[NUM_DISPLAY_BUFFERS];
static SceGxmSyncObject* gxm_sync_objects[NUM_DISPLAY_BUFFERS];

static SceUID gxm_depth_stencil_surface_uid;
static void*  gxm_depth_stencil_surface_addr;
static SceGxmDepthStencilSurface gxm_depth_stencil_surface;

static SceGxmShaderPatcher *gxm_shader_patcher;
static const int shader_patcher_buffer_size = 64 * 1024;
static SceUID gxm_shader_patcher_buffer_uid;
static void*  gxm_shader_patcher_buffer_addr;

static const int shader_patcher_vertex_usse_size = 64 * 1024;
static SceUID gxm_shader_patcher_vertex_usse_uid;
static void*  gxm_shader_patcher_vertex_usse_addr;
static unsigned int shader_patcher_vertex_usse_offset;

static const int shader_patcher_fragment_usse_size = 64 * 1024;
static SceUID gxm_shader_patcher_fragment_usse_uid;
static void*  gxm_shader_patcher_fragment_usse_addr;
static unsigned int shader_patcher_fragment_usse_offset;


#include "../../misc/vita/colored_f.h"
#include "../../misc/vita/colored_v.h"
static SceGxmProgram* gxm_colored_VP = (SceGxmProgram *)&colored_v;
static SceGxmProgram* gxm_colored_FP = (SceGxmProgram *)&colored_f;

#include "../../misc/vita/textured_f.h"
#include "../../misc/vita/textured_v.h"
static SceGxmProgram* gxm_textured_VP = (SceGxmProgram *)&textured_v;
static SceGxmProgram* gxm_textured_FP = (SceGxmProgram *)&textured_f;

#include "../../misc/vita/colored_alpha_f.h"
static SceGxmProgram* gxm_colored_alpha_FP = (SceGxmProgram *)&colored_alpha_f;
#include "../../misc/vita/textured_alpha_f.h"
static SceGxmProgram* gxm_textured_alpha_FP = (SceGxmProgram *)&textured_alpha_f;


typedef struct CCVertexProgram {
	SceGxmVertexProgram* programPatched;
	const SceGxmProgramParameter* param_in_position;
	const SceGxmProgramParameter* param_in_color;
	const SceGxmProgramParameter* param_in_texcoord;
	const SceGxmProgramParameter* param_uni_mvp;
	int dirtyUniforms;
} VertexProgram;
#define VP_UNI_MATRIX 0x01

typedef struct CCFragmentProgram {
	SceGxmFragmentProgram* programPatched;
} FragmentProgram;


/*########################################################################################################################*
*---------------------------------------------------------Memory----------------------------------------------------------*
*#########################################################################################################################*/
#define ALIGNUP(size, a) (((size) + ((a) - 1)) & ~((a) - 1))

void* AllocGPUMemory(int size, int type, int gpu_access, SceUID* ret_uid, const char* memType) {
	char buffer[128];
	cc_string str;
	void* addr;
	
	if (type == SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW) {
		size = ALIGNUP(size, 256 * 1024);
	} else {
		size = ALIGNUP(size, 4 * 1024);
	}
	String_InitArray_NT(str, buffer);
	
	// https://wiki.henkaku.xyz/vita/SceSysmem
	SceUID uid = sceKernelAllocMemBlock(memType, type, size, NULL);
	if (uid < 0) {
		String_Format2(&str, "Failed to allocate GPU memory block for %c (%i bytes)%N", memType, &size);
		Process_Abort2(uid, buffer);
	}
		
	int res1 = sceKernelGetMemBlockBase(uid, &addr);
	if (res1 < 0) {
		String_Format1(&str, "Failed to get base of GPU memory block for %c%N", memType);
		Process_Abort2(res1, buffer);
	}
		
	int res2 = sceGxmMapMemory(addr, size, gpu_access);
	if (res2 < 0) {
		String_Format2(&str, "Failed to map memory for GPU usage for %c (%i bytes)%N", memType, &size);
		Process_Abort2(res2, buffer);
	}
	// https://wiki.henkaku.xyz/vita/GPU
	
	*ret_uid = uid;
	return addr;
}

void* AllocGPUVertexUSSE(size_t size, SceUID* ret_uid, unsigned int* ret_usse_offset) {
	SceUID uid;
	void *addr;

	size = ALIGNUP(size, 4 * 1024);

	uid = sceKernelAllocMemBlock("GPU vertex USSE",
		SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, size, NULL);
	if (uid < 0) Process_Abort2(uid, "Failed to allocate vertex USSE block");

	int res1 = sceKernelGetMemBlockBase(uid, &addr);
	if (res1 < 0) Process_Abort2(res1, "Failed to get base of vertex USSE memory block");

	int res2 = sceGxmMapVertexUsseMemory(addr, size, ret_usse_offset);
	if (res1 < 0) Process_Abort2(res2, "Failed to map vertex USSE memory");

	*ret_uid = uid;
	return addr;
}

void* AllocGPUFragmentUSSE(size_t size, SceUID* ret_uid, unsigned int* ret_usse_offset) {
	SceUID uid;
	void *addr;

	size = ALIGNUP(size, 4 * 1024);

	uid = sceKernelAllocMemBlock("GPU fragment USSE",
		SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, size, NULL);
	if (uid < 0) Process_Abort2(uid, "Failed to allocate fragment USSE block");

	int res1 = sceKernelGetMemBlockBase(uid, &addr);
	if (res1 < 0) Process_Abort2(res1, "Failed to get base of fragment USSE memory block");

	int res2 = sceGxmMapFragmentUsseMemory(addr, size, ret_usse_offset);
	if (res1 < 0) Process_Abort2(res2, "Failed to map fragment USSE memory");

	*ret_uid = uid;
	return addr;
}

static void FreeGPUMemory(SceUID uid) {
	void *addr;

	if (sceKernelGetMemBlockBase(uid, &addr) < 0)
		return;

	sceGxmUnmapMemory(addr);
	sceKernelFreeMemBlock(uid);
}

static void* AllocShaderPatcherMem(void* user_data, unsigned int size) {
	return Mem_TryAlloc(1, size);
}

static void FreeShaderPatcherMem(void* user_data, void* mem) {
	Mem_Free(mem);
}


/*########################################################################################################################*
*-----------------------------------------------------Vertex shaders------------------------------------------------------*
*#########################################################################################################################*/
static VertexProgram VP_list[2];
static VertexProgram* VP_Active;
static float transposed_mvp[4*4] __attribute__((aligned(64)));

static void VP_DirtyUniform(int uniform) {
	for (int i = 0; i < Array_Elems(VP_list); i++) 
	{
		VP_list[i].dirtyUniforms |= uniform;
	}
}

static void VP_ReloadUniforms(void) {
	VertexProgram* VP = VP_Active;
	// Calling sceGxmReserveVertexDefaultUniformBuffer when not in a scene
	//   results in SCE_GXM_ERROR_NOT_WITHIN_SCENE on real hardware
	if (!VP || !in_scene) return;
	int ret;

	if (VP->dirtyUniforms & VP_UNI_MATRIX) {
		void *uniform_buffer = NULL;
		
		ret = sceGxmReserveVertexDefaultUniformBuffer(gxm_context, &uniform_buffer);
		if (ret) Process_Abort2(ret, "Reserving uniform buffer");
		ret = sceGxmSetUniformDataF(uniform_buffer, VP->param_uni_mvp, 0, 4 * 4, transposed_mvp);
		if (ret) Process_Abort2(ret, "Updating uniform buffer");
			
		VP->dirtyUniforms &= ~VP_UNI_MATRIX;
	}
}

static void VP_SwitchActive(void) {
	int index = gfx_format == VERTEX_FORMAT_TEXTURED ? 1 : 0;
	
	VertexProgram* VP = &VP_list[index];
	if (VP == VP_Active) return;
	VP_Active = VP;
	
	VP->dirtyUniforms |= VP_UNI_MATRIX; // Need to update uniforms after switching program
	sceGxmSetVertexProgram(gxm_context, VP->programPatched);
	VP_ReloadUniforms();
}


/*########################################################################################################################*
*----------------------------------------------------Fragment shaders-----------------------------------------------------*
*#########################################################################################################################*/
static FragmentProgram FP_list[4 * 3];
static FragmentProgram* FP_Active;

static void FP_SwitchActive(void) {
	int index = gfx_format == VERTEX_FORMAT_TEXTURED ? 3 : 0;
	
	// [normal rendering, blend rendering, no rendering]
	if (gfx_depthOnly) {
		index += 2;
	} else if (gfx_alphaBlend) {
		index += 1;
	}
	
	if (gfx_alphaTest) index += 2 * 3;
	
	FragmentProgram* FP = &FP_list[index];
	if (FP == FP_Active) return;
	FP_Active = FP;
	
	sceGxmSetFragmentProgram(gxm_context, FP->programPatched);
}


static const SceGxmBlendInfo no_blending = {
	SCE_GXM_COLOR_MASK_ALL,
	SCE_GXM_BLEND_FUNC_NONE,  SCE_GXM_BLEND_FUNC_NONE,
	SCE_GXM_BLEND_FACTOR_ONE, SCE_GXM_BLEND_FACTOR_ZERO,
	SCE_GXM_BLEND_FACTOR_ONE, SCE_GXM_BLEND_FACTOR_ZERO
};
static const SceGxmBlendInfo yes_blending = {
	SCE_GXM_COLOR_MASK_ALL,
	SCE_GXM_BLEND_FUNC_ADD,   SCE_GXM_BLEND_FUNC_ADD,
	SCE_GXM_BLEND_FACTOR_SRC_ALPHA, SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
	SCE_GXM_BLEND_FACTOR_SRC_ALPHA, SCE_GXM_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA
};
static const SceGxmBlendInfo no_rendering = {
	SCE_GXM_COLOR_MASK_NONE,
	SCE_GXM_BLEND_FUNC_NONE,  SCE_GXM_BLEND_FUNC_NONE,
	SCE_GXM_BLEND_FACTOR_ONE, SCE_GXM_BLEND_FACTOR_ZERO,
	SCE_GXM_BLEND_FACTOR_ONE, SCE_GXM_BLEND_FACTOR_ZERO
};
static const SceGxmBlendInfo* blend_modes[] = { &no_blending, &yes_blending, &no_rendering };

static void CreateFragmentPrograms(int index, const SceGxmProgram* fragProgram, const SceGxmProgram* vertexProgram) {
	SceGxmShaderPatcherId programID;
	
	for (int i = 0; i < Array_Elems(blend_modes); i++)
	{
		FragmentProgram* FP = &FP_list[index + i];
		sceGxmShaderPatcherRegisterProgram(gxm_shader_patcher, fragProgram, &programID);

		sceGxmShaderPatcherCreateFragmentProgram(gxm_shader_patcher,
			programID, SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4,
			SCE_GXM_MULTISAMPLE_NONE, blend_modes[i], vertexProgram,
			&FP->programPatched);
	}
}


/*########################################################################################################################*
*-----------------------------------------------------Initialisation------------------------------------------------------*
*#########################################################################################################################*/
struct DQCallbackData { void* addr; };
void (*DQ_OnNextFrame)(void* fb);

static void DQ_OnNextFrame3D(void* fb) {
	if (gfx_vsync) sceDisplayWaitVblankStart();
	
	GPUBuffers_DeleteUnreferenced();
	GPUTextures_DeleteUnreferenced();
	frameCounter++;
}

static void DQCallback(const void *callback_data) {
	SceDisplayFrameBuf fb = { 0 }; 

	fb.size   = sizeof(SceDisplayFrameBuf);
	fb.base   = ((struct DQCallbackData*)callback_data)->addr;
	fb.pitch  = DISPLAY_STRIDE;
	fb.pixelformat = SCE_DISPLAY_PIXELFORMAT_A8B8G8R8;
	fb.width  = DISPLAY_WIDTH;
	fb.height = DISPLAY_HEIGHT;

	sceDisplaySetFrameBuf(&fb, SCE_DISPLAY_SETBUF_NEXTFRAME);
	DQ_OnNextFrame(fb.base);
}

void Gfx_InitGXM(void) { // called from Window_Init
	SceGxmInitializeParams params = { 0 };
	
	params.displayQueueMaxPendingCount  = MAX_PENDING_SWAPS;
	params.displayQueueCallback         = DQCallback;
	params.displayQueueCallbackDataSize = sizeof(struct DQCallbackData);
	params.parameterBufferSize = SCE_GXM_DEFAULT_PARAMETER_BUFFER_SIZE;
	
	sceGxmInitialize(&params);
}

static void AllocRingBuffers(void) {
	vdm_ring_buffer_addr = AllocGPUMemory(SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE,
			SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ,
			&vdm_ring_buffer_uid, "VDM ring buffer");

	vertex_ring_buffer_addr = AllocGPUMemory(SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE,
			SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ,
			&vertex_ring_buffer_uid, "Vertex ring buffer");

	fragment_ring_buffer_addr = AllocGPUMemory(SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE,
			SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ,
			&fragment_ring_buffer_uid, "Fragment ring buffer");

	fragment_usse_ring_buffer_addr = AllocGPUFragmentUSSE(SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE,
			&fragment_ring_buffer_uid, &fragment_usse_offset);
}

static void AllocGXMContext(void) {
	SceGxmContextParams params = { 0 };
	
	params.hostMem     = Mem_Alloc(1, SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE, "Host memory");
	params.hostMemSize = SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE;
	
	params.vdmRingBufferMem     = vdm_ring_buffer_addr;
	params.vdmRingBufferMemSize = SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE;
	
	params.vertexRingBufferMem     = vertex_ring_buffer_addr;
	params.vertexRingBufferMemSize = SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE;
	
	params.fragmentRingBufferMem     = fragment_ring_buffer_addr;
	params.fragmentRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE;
	
	params.fragmentUsseRingBufferMem     = fragment_usse_ring_buffer_addr;
	params.fragmentUsseRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE;
	params.fragmentUsseRingBufferOffset  = fragment_usse_offset;

	sceGxmCreateContext(&params, &gxm_context);
}

static void AllocRenderTarget(void) {
	SceGxmRenderTargetParams params = { 0 };
	
	params.width  = DISPLAY_WIDTH;
	params.height = DISPLAY_HEIGHT;
	params.scenesPerFrame = 1;
	params.driverMemBlock = -1;

	sceGxmCreateRenderTarget(&params, &gxm_render_target);
}

static void AllocColorBuffer(int i) {
	int size = ALIGNUP(4 * DISPLAY_STRIDE * DISPLAY_HEIGHT, 1 * 1024 * 1024);
	
	gxm_color_surfaces_addr[i] = AllocGPUMemory(size, SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
									SCE_GXM_MEMORY_ATTRIB_RW, &gxm_color_surfaces_uid[i], "color buffer");

	sceGxmColorSurfaceInit(&gxm_color_surfaces[i],
		SCE_GXM_COLOR_FORMAT_A8B8G8R8,
		SCE_GXM_COLOR_SURFACE_LINEAR,
		SCE_GXM_COLOR_SURFACE_SCALE_NONE,
		SCE_GXM_OUTPUT_REGISTER_SIZE_32BIT,
		DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_STRIDE,
		gxm_color_surfaces_addr[i]);

	sceGxmSyncObjectCreate(&gxm_sync_objects[i]);
}

static void AllocDepthBuffer(void) {
	int width   = ALIGNUP(DISPLAY_WIDTH,  SCE_GXM_TILE_SIZEX);
	int height  = ALIGNUP(DISPLAY_HEIGHT, SCE_GXM_TILE_SIZEY);
	int samples = width * height;

	gxm_depth_stencil_surface_addr = AllocGPUMemory(4 * samples, SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW,
										SCE_GXM_MEMORY_ATTRIB_RW, &gxm_depth_stencil_surface_uid, "depth buffer");

	sceGxmDepthStencilSurfaceInit(&gxm_depth_stencil_surface,
		SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24,
		SCE_GXM_DEPTH_STENCIL_SURFACE_TILED,
		width, gxm_depth_stencil_surface_addr, NULL);
}

static void AllocShaderPatcherMemory(void) {
	gxm_shader_patcher_buffer_addr = AllocGPUMemory(shader_patcher_buffer_size, 
		SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ,
		&gxm_shader_patcher_buffer_uid, "shader patcher");

	gxm_shader_patcher_vertex_usse_addr = AllocGPUVertexUSSE(
		shader_patcher_vertex_usse_size, &gxm_shader_patcher_vertex_usse_uid,
		&shader_patcher_vertex_usse_offset);

	gxm_shader_patcher_fragment_usse_addr = AllocGPUFragmentUSSE(
		shader_patcher_fragment_usse_size, &gxm_shader_patcher_fragment_usse_uid,
		&shader_patcher_fragment_usse_offset);
}

static void AllocShaderPatcher(void) {
	SceGxmShaderPatcherParams params = { 0 };
	params.hostAllocCallback = AllocShaderPatcherMem;
	params.hostFreeCallback  = FreeShaderPatcherMem;
	
	params.bufferMem     = gxm_shader_patcher_buffer_addr;
	params.bufferMemSize = shader_patcher_buffer_size;
	
	params.vertexUsseMem     = gxm_shader_patcher_vertex_usse_addr;
	params.vertexUsseMemSize = shader_patcher_vertex_usse_size;
	params.vertexUsseOffset  = shader_patcher_vertex_usse_offset;
	
	params.fragmentUsseMem     = gxm_shader_patcher_fragment_usse_addr;
	params.fragmentUsseMemSize = shader_patcher_fragment_usse_size;
	params.fragmentUsseOffset  = shader_patcher_fragment_usse_offset;

	sceGxmShaderPatcherCreate(&params, &gxm_shader_patcher);
}

static void AllocColouredVertexProgram(int index) {
	SceGxmShaderPatcherId programID;
	VertexProgram* VP = &VP_list[index];
	sceGxmShaderPatcherRegisterProgram(gxm_shader_patcher, gxm_colored_VP, &programID);

	const SceGxmProgram* prog = sceGxmShaderPatcherGetProgramFromId(programID);

	VP->param_in_position = sceGxmProgramFindParameterByName(prog, "in_position");
	VP->param_in_color    = sceGxmProgramFindParameterByName(prog, "in_color");
	VP->param_uni_mvp     = sceGxmProgramFindParameterByName(prog, "mvp_matrix");

	SceGxmVertexAttribute attribs[2];
	SceGxmVertexStream vertex_stream;
	
	attribs[0].streamIndex    = 0;
	attribs[0].offset         = 0;
	attribs[0].format         = SCE_GXM_ATTRIBUTE_FORMAT_F32;
	attribs[0].componentCount = 3;
	attribs[0].regIndex       = sceGxmProgramParameterGetResourceIndex(VP->param_in_position);
		
	attribs[1].streamIndex    = 0;
	attribs[1].offset         = 3 * sizeof(float);
	attribs[1].format         = SCE_GXM_ATTRIBUTE_FORMAT_U8N;
	attribs[1].componentCount = 4;
	attribs[1].regIndex       = sceGxmProgramParameterGetResourceIndex(VP->param_in_color);
		
	vertex_stream.stride      = SIZEOF_VERTEX_COLOURED;
	vertex_stream.indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT;

	sceGxmShaderPatcherCreateVertexProgram(gxm_shader_patcher,
		programID, attribs, 2,
		&vertex_stream, 1, &VP->programPatched);
}

static void AllocTexturedVertexProgram(int index) {
	SceGxmShaderPatcherId programID;
	VertexProgram* VP = &VP_list[index];
	sceGxmShaderPatcherRegisterProgram(gxm_shader_patcher, gxm_textured_VP, &programID);

	const SceGxmProgram* prog = sceGxmShaderPatcherGetProgramFromId(programID);

	VP->param_in_position = sceGxmProgramFindParameterByName(prog, "in_position");
	VP->param_in_color    = sceGxmProgramFindParameterByName(prog, "in_color");
	VP->param_in_texcoord = sceGxmProgramFindParameterByName(prog, "in_texcoord");
	VP->param_uni_mvp     = sceGxmProgramFindParameterByName(prog, "mvp_matrix");

	SceGxmVertexAttribute attribs[3];
	SceGxmVertexStream vertex_stream;
	
	attribs[0].streamIndex    = 0;
	attribs[0].offset         = 0;
	attribs[0].format         = SCE_GXM_ATTRIBUTE_FORMAT_F32;
	attribs[0].componentCount = 3;
	attribs[0].regIndex       = sceGxmProgramParameterGetResourceIndex(VP->param_in_position);
		
	attribs[1].streamIndex    = 0;
	attribs[1].offset         = 3 * sizeof(float);
	attribs[1].format         = SCE_GXM_ATTRIBUTE_FORMAT_U8N;
	attribs[1].componentCount = 4;
	attribs[1].regIndex       = sceGxmProgramParameterGetResourceIndex(VP->param_in_color);
		
	attribs[2].streamIndex    = 0;
	attribs[2].offset         = 3 * sizeof(float) + 4 * sizeof(char);
	attribs[2].format         = SCE_GXM_ATTRIBUTE_FORMAT_F32;
	attribs[2].componentCount = 2;
	attribs[2].regIndex       = sceGxmProgramParameterGetResourceIndex(VP->param_in_texcoord);
		
	vertex_stream.stride      = SIZEOF_VERTEX_TEXTURED;
	vertex_stream.indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT;

	sceGxmShaderPatcherCreateVertexProgram(gxm_shader_patcher,
		programID, attribs, 3,
		&vertex_stream, 1, &VP->programPatched);
}

/*########################################################################################################################*
*---------------------------------------------------------General---------------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID white_square;
static void SetDefaultStates(void) {
	sceGxmSetFrontDepthFunc(gxm_context, SCE_GXM_DEPTH_FUNC_LESS_EQUAL);
	sceGxmSetBackDepthFunc(gxm_context,  SCE_GXM_DEPTH_FUNC_LESS_EQUAL);
}

void Gfx_AllocFramebuffers(void) { // called from Window_Init
	for (int i = 0; i < NUM_DISPLAY_BUFFERS; i++) 
	{
		AllocColorBuffer(i);
	}
	AllocDepthBuffer();
	
	frontBufferIndex = NUM_DISPLAY_BUFFERS - 1;
	backBufferIndex  = 0;
}

static void InitGPU(void) {
	AllocRingBuffers();
	AllocGXMContext();
	
	AllocRenderTarget();
	AllocShaderPatcherMemory();
	AllocShaderPatcher();
	
	AllocColouredVertexProgram(0);
	CreateFragmentPrograms(0, gxm_colored_FP,        gxm_colored_VP);
	CreateFragmentPrograms(6, gxm_colored_alpha_FP,  gxm_colored_VP);
	AllocTexturedVertexProgram(1);
	CreateFragmentPrograms(3, gxm_textured_FP,       gxm_textured_VP);
	CreateFragmentPrograms(9, gxm_textured_alpha_FP, gxm_textured_VP);
}

void Gfx_Create(void) {
	DQ_OnNextFrame = DQ_OnNextFrame3D;
	if (!Gfx.Created) InitGPU();
	in_scene = false;
	
	Gfx.MaxTexWidth  = 1024;
	Gfx.MaxTexHeight = 1024;
	Gfx.MaxTexSize   = 512 * 512;
	Gfx.Created      = true;
	gfx_vsync        = true;
	
	Gfx_SetDepthTest(true);
	Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);
	Gfx_RestoreState();
}

void Gfx_Free(void) { 
	Gfx_FreeState();
}

cc_bool Gfx_TryRestoreContext(void) { return true; }

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);
	// TODO
}

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


/*########################################################################################################################*
*--------------------------------------------------------GPU Textures-----------------------------------------------------*
*#########################################################################################################################*/
struct GPUTexture;
struct GPUTexture {
	cc_uint32* data;
	SceUID uid;
	SceGxmTexture texture;
	struct GPUTexture* next;
	cc_uint32 lastFrame;
};
static struct GPUTexture* del_textures_head;
static struct GPUTexture* del_textures_tail;

struct GPUTexture* GPUTexture_Alloc(int size) {
	struct GPUTexture* tex = Mem_AllocCleared(1, sizeof(struct GPUTexture), "GPU texture");
	
	tex->data = AllocGPUMemory(size, 
		SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCE_GXM_MEMORY_ATTRIB_READ,
		&tex->uid, "texture");
	return tex;
}

// can't delete textures until not used in any frames
static void GPUTexture_Unref(GfxResourceID* resource) {
	struct GPUTexture* tex = (struct GPUTexture*)(*resource);
	if (!tex) return;
	*resource = NULL;

	LinkedList_Append(tex, del_textures_head, del_textures_tail);
}

static void GPUTexture_Free(struct GPUTexture* tex) {
	FreeGPUMemory(tex->uid);
	Mem_Free(tex);
}

static void GPUTextures_DeleteUnreferenced(void) {
	if (!del_textures_head) return;
	
	struct GPUTexture* tex;
	struct GPUTexture* next;
	struct GPUTexture* prev = NULL;
	
	for (tex = del_textures_head; tex != NULL; tex = next)
	{
		next = tex->next;
		
		if (tex->lastFrame + 4 > frameCounter) {
			// texture was used within last 4 fames
			prev = tex;
		} else {
			// advance the head of the linked list
			if (del_textures_head == tex) 
				del_textures_head = next;

			// update end of linked list if necessary
			if (del_textures_tail == tex)
				del_textures_tail = prev;
			
			// unlink this texture from the linked list
			if (prev) prev->next = next;
			
			GPUTexture_Free(tex);
		}
	}
}

// See Graphics_Dreamcast.c for twiddling explanation
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 += 0x01 << shift;
			*maskX += 0x02 << 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;		
		}
	}
}


/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
	int size = bmp->width * bmp->height * 4;
	struct GPUTexture* tex = GPUTexture_Alloc(size);
	cc_uint32* dst = tex->data;

	int width = bmp->width, height = bmp->height;
	unsigned maskX, maskY;
	unsigned X = 0, Y = 0;
	TwiddleCalcFactors(width, height, &maskX, &maskY);
	
	for (int y = 0; y < height; y++)
	{
		cc_uint32* src = bmp->scan0 + y * rowWidth;
		X = 0;
		
		for (int x = 0; x < width; x++, src++)
		{
			dst[X | Y] = *src;
			X = (X - maskX) & maskX;
		}
		Y = (Y - maskY) & maskY;
	}
            
	sceGxmTextureInitSwizzled(&tex->texture, dst,
		SCE_GXM_TEXTURE_FORMAT_A8B8G8R8, width, height, 0);
		
	sceGxmTextureSetUAddrMode(&tex->texture, SCE_GXM_TEXTURE_ADDR_REPEAT);
	sceGxmTextureSetVAddrMode(&tex->texture, SCE_GXM_TEXTURE_ADDR_REPEAT);
	return tex;
}

void Gfx_UpdateTexture(GfxResourceID texId, int originX, int originY, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
	struct GPUTexture* tex = (struct GPUTexture*)texId;
	int texWidth   = sceGxmTextureGetWidth(&tex->texture);
	int texHeight  = sceGxmTextureGetHeight(&tex->texture);
	cc_uint32* dst = tex->data;
	
	int width = part->width, height = part->height;
	unsigned maskX, maskY;
	unsigned X = 0, Y = 0;
	TwiddleCalcFactors(texWidth, texHeight, &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;
	}

	// TODO: is it necessary to invalidate? probably just everything?
	//sceKernelDcacheWritebackInvalidateRange(dst, (tex->width * part->height) * 4);
}

void Gfx_DeleteTexture(GfxResourceID* texId) {
	GPUTexture_Unref(texId);
}

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

void Gfx_BindTexture(GfxResourceID texId) {
	if (!texId) texId = white_square;
 
 	struct GPUTexture* tex = (struct GPUTexture*)texId;
 	tex->lastFrame = frameCounter;
	sceGxmSetFragmentTexture(gxm_context, 0, &tex->texture);
}


/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
	// Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glortho
	//   The simplified calculation below uses: L = 0, R = width, T = 0, B = height
	// NOTE: Shared with OpenGL. might be wrong to do that though?
	*matrix = Matrix_Identity;

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

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

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);

	// Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glfrustum
	// For a FOV based perspective matrix, left/right/top/bottom are calculated as:
	//   left = -c * aspect, right = c * aspect, bottom = -c, top = c
	// Calculations are simplified because of left/right and top/bottom symmetry
	*matrix = Matrix_Identity;

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


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

void Gfx_GetApiInfo(cc_string* info) {
	String_AppendConst(info, "-- Using PS Vita --\n");
	PrintMaxTextureInfo(info);
}

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

void Gfx_BeginFrame(void) {
	in_scene = true;
	sceGxmBeginScene(gxm_context,
			0, gxm_render_target,
			NULL, NULL,
			gxm_sync_objects[backBufferIndex],
			&gxm_color_surfaces[backBufferIndex],
			&gxm_depth_stencil_surface);
}

void Gfx_UpdateCommonDialogBuffers(void) {
	SceCommonDialogUpdateParam param = { 0 };
	param.renderTarget.colorFormat   = SCE_GXM_COLOR_FORMAT_A8B8G8R8;
	param.renderTarget.surfaceType   = SCE_GXM_COLOR_SURFACE_LINEAR;
	param.renderTarget.width         = DISPLAY_WIDTH;
	param.renderTarget.height        = DISPLAY_HEIGHT;
	param.renderTarget.strideInPixels   = DISPLAY_STRIDE;
	param.renderTarget.colorSurfaceData = gxm_color_surfaces_addr[backBufferIndex];
	param.renderTarget.depthSurfaceData = gxm_depth_stencil_surface.depthData;
	param.displaySyncObject = gxm_sync_objects[backBufferIndex];
	
	sceCommonDialogUpdate(&param);
}

void Gfx_NextFramebuffer(void) {
	struct DQCallbackData cb_data;
	cb_data.addr = gxm_color_surfaces_addr[backBufferIndex];

	sceGxmDisplayQueueAddEntry(gxm_sync_objects[frontBufferIndex],
			gxm_sync_objects[backBufferIndex], &cb_data);

	// Cycle through to next buffer pair
	frontBufferIndex = backBufferIndex;
	backBufferIndex  = (backBufferIndex + 1) % NUM_DISPLAY_BUFFERS;
}

void Gfx_EndFrame(void) {
	in_scene = false;
	sceGxmEndScene(gxm_context, NULL, NULL);

	Gfx_NextFramebuffer();
}

void Gfx_OnWindowResize(void) { }

void Gfx_SetViewport(int x, int y, int w, int h) { }
void Gfx_SetScissor (int x, int y, int w, int h) { }


/*########################################################################################################################*
*--------------------------------------------------------GPU Buffers------------------------------------------------------*
*#########################################################################################################################*/
struct GPUBuffer;
struct GPUBuffer {
	void* data;
	SceUID uid;
	cc_uint32 lastFrame;
	struct GPUBuffer* next;
};
static struct GPUBuffer* del_buffers_head;
static struct GPUBuffer* del_buffers_tail;

struct GPUBuffer* GPUBuffer_Alloc(int size) {
	struct GPUBuffer* buffer = Mem_AllocCleared(1, sizeof(struct GPUBuffer), "GPU buffer");
	
	buffer->data = AllocGPUMemory(size, 
		SCE_KERNEL_MEMBLOCK_TYPE_USER_RW_UNCACHE, SCE_GXM_MEMORY_ATTRIB_READ,
		&buffer->uid, "buffer");
	return buffer;
}


// can't delete buffers until not used in any frames
static void GPUBuffer_Unref(GfxResourceID* resource) {
	struct GPUBuffer* buf = (struct GPUBuffer*)(*resource);
	if (!buf) return;
	*resource = NULL;
	
	LinkedList_Append(buf, del_buffers_head, del_buffers_tail);
}

static void GPUBuffer_Free(struct GPUBuffer* buf) {
	FreeGPUMemory(buf->uid);
	Mem_Free(buf);
}

static void GPUBuffers_DeleteUnreferenced(void) {
	if (!del_buffers_head) return;
	
	struct GPUBuffer* buf;
	struct GPUBuffer* next;
	struct GPUBuffer* prev = NULL;
	
	for (buf = del_buffers_head; buf != NULL; buf = next)
	{
		next = buf->next;
		
		if (buf->lastFrame + 4 > frameCounter) {
			// texture was used within last 4 fames
			prev = buf;
		} else {
			// advance the head of the linked list
			if (del_buffers_head == buf) 
				del_buffers_head = next;

			// update end of linked list if necessary
			if (del_buffers_tail == buf)
				del_buffers_tail = prev;
			
			// unlink this texture from the linked list
			if (prev) prev->next = next;
			
			GPUBuffer_Free(buf);
		}
	}
}


/*########################################################################################################################*
*-------------------------------------------------------Index buffers-----------------------------------------------------*
*#########################################################################################################################*/
static cc_uint16* gfx_indices;

GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
	struct GPUBuffer* buffer = GPUBuffer_Alloc(count * 2);
	fillFunc(buffer->data, count, obj);
	return buffer;
}

void Gfx_BindIb(GfxResourceID ib) {
	struct GPUBuffer* buffer = (struct GPUBuffer*)ib;
	gfx_indices = buffer->data;
}

void Gfx_DeleteIb(GfxResourceID* ib) { GPUBuffer_Unref(ib); }


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

void Gfx_BindVb(GfxResourceID vb) { 
	struct GPUBuffer* buffer = (struct GPUBuffer*)vb;
	buffer->lastFrame = frameCounter;
	sceGxmSetVertexStream(gxm_context, 0, buffer->data);
}

void Gfx_DeleteVb(GfxResourceID* vb) { GPUBuffer_Unref(vb); }

void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
	struct GPUBuffer* buffer = (struct GPUBuffer*)vb;
	return buffer->data;
}

void Gfx_UnlockVb(GfxResourceID vb) { Gfx_BindVb(vb); }


static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
	return GPUBuffer_Alloc(maxVertices * strideSizes[fmt]);
}

void Gfx_BindDynamicVb(GfxResourceID vb) { Gfx_BindVb(vb); }

void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
	struct GPUBuffer* buffer = (struct GPUBuffer*)vb;
	return buffer->data;
}

void Gfx_UnlockDynamicVb(GfxResourceID vb) { Gfx_BindVb(vb); }

void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); }


/*########################################################################################################################*
*-----------------------------------------------------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
}

static void SetAlphaTest(cc_bool enabled) {
	FP_SwitchActive();
}
 
static void SetAlphaBlend(cc_bool enabled) {
	FP_SwitchActive();
}

void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
	// TODO
	gfx_depthOnly = depthOnly;
	FP_SwitchActive();
}

void Gfx_SetFaceCulling(cc_bool enabled) { 
	sceGxmSetCullMode(gxm_context, enabled ? SCE_GXM_CULL_CW : SCE_GXM_CULL_NONE);
}


void Gfx_SetAlphaArgBlend(cc_bool enabled) { }

static PackedCol clear_color;
void Gfx_ClearColor(PackedCol color) {
	clear_color = color;
}

static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
 // TODO
}

void Gfx_SetDepthWrite(cc_bool enabled) {
	int mode = enabled ? SCE_GXM_DEPTH_WRITE_ENABLED : SCE_GXM_DEPTH_WRITE_DISABLED;
	sceGxmSetFrontDepthWriteEnable(gxm_context, mode);
	sceGxmSetBackDepthWriteEnable(gxm_context,  mode);
}

void Gfx_SetDepthTest(cc_bool enabled) {
	int func = enabled ? SCE_GXM_DEPTH_FUNC_LESS_EQUAL : SCE_GXM_DEPTH_FUNC_ALWAYS;
	sceGxmSetFrontDepthFunc(gxm_context, func);
	sceGxmSetBackDepthFunc(gxm_context,  func);
}


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

void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
	if (type == MATRIX_VIEW) _view = *matrix;
	if (type == MATRIX_PROJ) _proj = *matrix;

	struct Matrix mvp __attribute__((aligned(64)));	
	Matrix_Mul(&mvp, &_view, &_proj);
	float* m = &mvp;
	
	// Transpose matrix
	for (int i = 0; i < 4; i++)
	{
		transposed_mvp[i * 4 + 0] = m[0  + i];
		transposed_mvp[i * 4 + 1] = m[4  + i];
		transposed_mvp[i * 4 + 2] = m[8  + i];
		transposed_mvp[i * 4 + 3] = m[12 + i];
	}
	
	VP_DirtyUniform(VP_UNI_MATRIX);
	VP_ReloadUniforms();
}

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) {
 // TODO
}

void Gfx_DisableTextureOffset(void) { 
 // TODO
}



/*########################################################################################################################*
*---------------------------------------------------------Drawing---------------------------------------------------------*
*#########################################################################################################################*/
cc_bool Gfx_WarnIfNecessary(void) { return false; }
cc_bool Gfx_GetUIOptions(struct MenuOptionsScreen* s) { return false; }

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) {
 // TODO
}

// TODO probably wrong to offset index buffer
void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex, DrawHints hints) {
	//Platform_Log2("DRAW1: %i, %i", &verticesCount, &startVertex); Thread_Sleep(100);
	sceGxmDraw(gxm_context, SCE_GXM_PRIMITIVE_TRIANGLES,
			SCE_GXM_INDEX_FORMAT_U16, gfx_indices + ICOUNT(startVertex), ICOUNT(verticesCount));
}

// TODO probably wrong to offset index buffer
void Gfx_DrawVb_IndexedTris(int verticesCount) {
	//Platform_Log1("DRAW2: %i", &verticesCount); Thread_Sleep(100);
	sceGxmDraw(gxm_context, SCE_GXM_PRIMITIVE_TRIANGLES,
			SCE_GXM_INDEX_FORMAT_U16, gfx_indices, ICOUNT(verticesCount));
}

// TODO probably wrong to offset index buffer
void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
	//Platform_Log2("DRAW3: %i, %i", &verticesCount, &startVertex); Thread_Sleep(100);
	sceGxmDraw(gxm_context, SCE_GXM_PRIMITIVE_TRIANGLES,
			SCE_GXM_INDEX_FORMAT_U16, gfx_indices + ICOUNT(startVertex), ICOUNT(verticesCount));
}


void Gfx_ClearBuffers(GfxBuffers buffers) {
	// TODO clear only some buffers
	static struct GPUBuffer* clearVB;
	if (!clearVB) {
		clearVB = GPUBuffer_Alloc(4 * sizeof(struct VertexColoured));
	}
	
	struct VertexColoured* clear_vertices = clearVB->data;
	clear_vertices[0] = (struct VertexColoured){-1.0f, -1.0f, 1.0f, clear_color };
	clear_vertices[1] = (struct VertexColoured){ 1.0f, -1.0f, 1.0f, clear_color };
	clear_vertices[2] = (struct VertexColoured){ 1.0f,  1.0f, 1.0f, clear_color };
	clear_vertices[3] = (struct VertexColoured){-1.0f,  1.0f, 1.0f, clear_color };
	
	Gfx_SetAlphaTest(false);
	Gfx_SetDepthTest(false);
	
	Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);
	Gfx_LoadMatrix(MATRIX_VIEW, &Matrix_Identity);
	Gfx_LoadMatrix(MATRIX_PROJ, &Matrix_Identity);
	Gfx_BindVb(clearVB);
	Gfx_DrawVb_IndexedTris(4);
	
	Gfx_SetDepthTest(true);
}

