#include <assert.h>
#include <stdint.h>
#include <memory.h>
#include <algorithm>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include "OpenGL.h"
#include "Textures.h"
#include "GLSLCombiner.h"
#include "GBI.h"
#include "RSP.h"
#include "gDP.h"
#include "gSP.h"
#include "N64.h"
#include "convert.h"
#include "FrameBuffer.h"
#include "Config.h"
#include "GLideNHQ/Ext_TxFilter.h"

using namespace std;

const GLuint g_noiseTexIndex = 2;
const GLuint g_MSTex0Index = g_noiseTexIndex + 1;

inline uint32_t GetNone( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return 0x00000000;
}

inline uint32_t GetCI4IA_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	if (x & 1)
		return IA88_RGBA4444( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] );
	else
		return IA88_RGBA4444( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B >> 4)] );
}

inline uint32_t GetCI4IA_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	if (x & 1)
		return IA88_RGBA8888( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] );
	else
		return IA88_RGBA8888( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B >> 4)] );
}

inline uint32_t GetCI4RGBA_RGBA5551( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	if (x & 1)
		return RGBA5551_RGBA5551( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] );
	else
		return RGBA5551_RGBA5551( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B >> 4)] );
}

inline uint32_t GetCI4RGBA_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	if (x & 1)
		return RGBA5551_RGBA8888( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B & 0x0F)] );
	else
		return RGBA5551_RGBA8888( *(uint16_t*)&TMEM[256 + (palette << 4) + (color4B >> 4)] );
}

inline uint32_t GetIA31_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	return IA31_RGBA8888( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) );
}

inline uint32_t GetIA31_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	return IA31_RGBA4444( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) );
}

inline uint32_t GetI4_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	return I4_RGBA8888( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) );
}

inline uint32_t GetI4_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint8_t color4B;

	color4B = ((uint8_t*)src)[(x>>1)^(i<<1)];

	return I4_RGBA4444( (x & 1) ? (color4B & 0x0F) : (color4B >> 4) );
}

inline uint32_t GetCI8IA_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA88_RGBA4444( *(uint16_t*)&TMEM[256 + ((uint8_t*)src)[x^(i<<1)]] );
}

inline uint32_t GetCI8IA_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA88_RGBA8888( *(uint16_t*)&TMEM[256 + ((uint8_t*)src)[x^(i<<1)]] );
}

inline uint32_t GetCI8RGBA_RGBA5551( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return RGBA5551_RGBA5551( *(uint16_t*)&TMEM[256 + ((uint8_t*)src)[x^(i<<1)]] );
}

inline uint32_t GetCI8RGBA_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return RGBA5551_RGBA8888( *(uint16_t*)&TMEM[256 + ((uint8_t*)src)[x^(i<<1)]] );
}

inline uint32_t GetIA44_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA44_RGBA8888(((uint8_t*)src)[x^(i<<1)]);
}

inline uint32_t GetIA44_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA44_RGBA4444(((uint8_t*)src)[x^(i<<1)]);
}

inline uint32_t GetI8_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return I8_RGBA8888(((uint8_t*)src)[x^(i<<1)]);
}

inline uint32_t GetI8_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return I8_RGBA4444(((uint8_t*)src)[x^(i<<1)]);
}

inline uint32_t GetCI16IA_RGBA8888(uint64_t *src, uint16_t x, uint16_t i, uint8_t palette)
{
	const uint16_t tex = ((uint16_t*)src)[x^i];
	const uint16_t col = (*(uint16_t*)&TMEM[256 + (tex >> 8)]);
	const uint16_t c = col >> 8;
	const uint16_t a = col & 0xFF;
	return (a << 24) | (c << 16) | (c << 8) | c;
}

inline uint32_t GetCI16IA_RGBA4444(uint64_t *src, uint16_t x, uint16_t i, uint8_t palette)
{
	const uint16_t tex = ((uint16_t*)src)[x^i];
	const uint16_t col = (*(uint16_t*)&TMEM[256 + (tex >> 8)]);
	const uint16_t c = col >> 12;
	const uint16_t a = col & 0x0F;
	return (a << 12) | (c << 8) | (c << 4) | c;
}

inline uint32_t GetCI16RGBA_RGBA8888(uint64_t *src, uint16_t x, uint16_t i, uint8_t palette)
{
	const uint16_t tex = (((uint16_t*)src)[x^i])&0xFF;
	return RGBA5551_RGBA8888(((uint16_t*)&TMEM[256])[tex << 2]);
}

inline uint32_t GetCI16RGBA_RGBA5551(uint64_t *src, uint16_t x, uint16_t i, uint8_t palette)
{
	const uint16_t tex = (((uint16_t*)src)[x^i]) & 0xFF;
	return RGBA5551_RGBA5551(((uint16_t*)&TMEM[256])[tex << 2]);
}

inline uint32_t GetRGBA5551_RGBA8888(uint64_t *src, uint16_t x, uint16_t i, uint8_t palette)
{
	uint16_t tex = ((uint16_t*)src)[x^i];
	return RGBA5551_RGBA8888(tex);
}

inline uint32_t GetRGBA5551_RGBA5551( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	uint16_t tex = ((uint16_t*)src)[x^i];
	return RGBA5551_RGBA5551(tex);
}

inline uint32_t GetIA88_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA88_RGBA8888(((uint16_t*)src)[x^i]);
}

inline uint32_t GetIA88_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return IA88_RGBA4444(((uint16_t*)src)[x^i]);
}

inline uint32_t GetRGBA8888_RGBA8888( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return ((uint32_t*)src)[x^i];
}

inline uint32_t GetRGBA8888_RGBA4444( uint64_t *src, uint16_t x, uint16_t i, uint8_t palette )
{
	return RGBA8888_RGBA4444(((uint32_t*)src)[x^i]);
}

uint32_t YUV_RGBA8888(uint8_t y, uint8_t u, uint8_t v)
{
	int32_t r = (int32_t)(y + (1.370705f * (v - 128)));
	int32_t g = (int32_t)((y - (0.698001f * (v - 128)) - (0.337633f * (u - 128))));
	int32_t b = (int32_t)(y + (1.732446f * (u - 128)));
	//clipping the result
	if (r > 255) r = 255;
	if (g > 255) g = 255;
	if (b > 255) b = 255;
	if (r < 0) r = 0;
	if (g < 0) g = 0;
	if (b < 0) b = 0;

	return (0xff << 24) | (b << 16) | (g << 8) | r;
}

uint16_t YUV_RGBA4444(uint8_t y, uint8_t u, uint8_t v)
{
	return RGBA8888_RGBA4444(YUV_RGBA8888(y, u, v));
}

inline void GetYUV_RGBA8888(uint64_t * src, uint32_t * dst, uint16_t x)
{
	const uint32_t t = (((uint32_t*)src)[x]);
	uint8_t y1 = (uint8_t)t & 0xFF;
	uint8_t v = (uint8_t)(t >> 8) & 0xFF;
	uint8_t y0 = (uint8_t)(t >> 16) & 0xFF;
	uint8_t u = (uint8_t)(t >> 24) & 0xFF;
	uint32_t c = YUV_RGBA8888(y0, u, v);
	*(dst++) = c;
	c = YUV_RGBA8888(y1, u, v);
	*(dst++) = c;
}

inline void GetYUV_RGBA4444(uint64_t * src, uint16_t * dst, uint16_t x)
{
	const uint32_t t = (((uint32_t*)src)[x]);
	uint8_t y1 = (uint8_t)t & 0xFF;
	uint8_t v = (uint8_t)(t >> 8) & 0xFF;
	uint8_t y0 = (uint8_t)(t >> 16) & 0xFF;
	uint8_t u = (uint8_t)(t >> 24) & 0xFF;
	uint16_t c = YUV_RGBA4444(y0, u, v);
	*(dst++) = c;
	c = YUV_RGBA4444(y1, u, v);
	*(dst++) = c;
}

const struct TextureLoadParameters
{
	GetTexelFunc	Get16;
	GLenum			glType16;
	GLint			glInternalFormat16;
	GetTexelFunc	Get32;
	GLenum			glType32;
	GLint			glInternalFormat32;
	uint32_t				autoFormat, lineShift, maxTexels;
} imageFormat[4][4][5] =
{ // G_TT_NONE
	{ //		Get16					glType16	glInternalFormat16		Get32					glType32	glInternalFormat32	autoFormat
		{ // 4-bit
			{ GetI4_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI4_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // RGBA as I
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // YUV
			{ GetI4_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI4_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // CI without palette
			{ GetIA31_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetIA31_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // IA
			{ GetI4_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI4_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 4, 8192 }, // I
		},
		{ // 8-bit
			{ GetI8_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI8_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  3, 4096 }, // RGBA as I
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 4096 }, // YUV
			{ GetI8_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI8_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  3, 4096 }, // CI without palette
			{ GetIA44_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetIA44_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 3, 4096 }, // IA
			{ GetI8_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetI8_RGBA8888,			GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  3, 4096 }, // I
		},
		{ // 16-bit
			{ GetRGBA5551_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1, GL_RGB5_A1,	GetRGBA5551_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	2, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	2, 2048 }, // YUV
			{ GetIA88_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetIA88_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,		2, 2048 }, // CI as IA
			{ GetIA88_RGBA4444,		GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetIA88_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,		2, 2048 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	0, 2048 }, // I
		},
		{ // 32-bit
			{ GetRGBA8888_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetRGBA8888_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  2, 1024 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4, GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // I
		}
	},
	// DUMMY
	{ //		Get16					glType16	glInternalFormat16			Get32				glType32	glInternalFormat32	autoFormat
		{ // 4-bit
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	4, 4096 }, // CI (Banjo-Kazooie uses this, doesn't make sense, but it works...)
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	4, 8192 }, // YUV
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	4, 4096 }, // CI
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	4, 4096 }, // IA as CI
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	4, 4096 }, // I as CI
		},
		{ // 8-bit
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,   0, 4096 }, // YUV
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // CI
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // IA as CI
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // I as CI
		},
		{ // 16-bit
			{ GetCI16RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetRGBA5551_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	2, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	2, 2048 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	0, 2048 }, // CI
			{ GetCI16RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI16RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	2, 2048 }, // IA as CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	0, 2048 }, // I
		},
		{ // 32-bit
			{ GetRGBA8888_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetRGBA8888_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  2, 1024 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // I
		}
	},
	// G_TT_RGBA16
	{ //		Get16					glType16			glInternalFormat16	Get32				glType32	glInternalFormat32	autoFormat
		{ // 4-bit
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // CI (Banjo-Kazooie uses this, doesn't make sense, but it works...)
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,   4, 8192 }, // YUV
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // CI
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // IA as CI
			{ GetCI4RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI4RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 4, 4096 }, // I as CI
		},
		{ // 8-bit
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,   0, 4096 }, // YUV
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // CI
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // IA as CI
			{ GetCI8RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI8RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1, 3, 2048 }, // I as CI
		},
		{ // 16-bit
			{ GetCI16RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetRGBA5551_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	2, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	2, 2048 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	0, 2048 }, // CI
			{ GetCI16RGBA_RGBA5551,	GL_UNSIGNED_SHORT_5_5_5_1,	GL_RGB5_A1,	GetCI16RGBA_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGB5_A1,	2, 2048 }, // IA as CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4,	0, 2048 }, // I
		},
		{ // 32-bit
			{ GetRGBA8888_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetRGBA8888_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA,  2, 1024 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA4, 0, 1024 }, // I
		}
	},
	// G_TT_IA16
	{ //		Get16					glType16			glInternalFormat16	Get32				glType32	glInternalFormat32	autoFormat
		{ // 4-bit
			{ GetCI4IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI4IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 4, 4096 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 4, 8192 }, // YUV
			{ GetCI4IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI4IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 4, 4096 }, // CI
			{ GetCI4IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI4IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 4, 4096 }, // IA
			{ GetCI4IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI4IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 4, 4096 }, // I
		},
		{ // 8-bit
			{ GetCI8IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI8IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 3, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 4096 }, // YUV
			{ GetCI8IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI8IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 3, 2048 }, // CI
			{ GetCI8IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI8IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 3, 2048 }, // IA
			{ GetCI8IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI8IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 3, 2048 }, // I
		},
		{ // 16-bit
			{ GetCI16IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI16IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 2048 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 2048 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 2048 }, // CI
			{ GetCI16IA_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetCI16IA_RGBA8888,		GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 2048 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 2048 }, // I
		},
		{ // 32-bit
			{ GetRGBA8888_RGBA4444,	GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetRGBA8888_RGBA8888,	GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 2, 1024 }, // RGBA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 1024 }, // YUV
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 1024 }, // CI
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 1024 }, // IA
			{ GetNone,				GL_UNSIGNED_SHORT_4_4_4_4,	GL_RGBA4,	GetNone,				GL_UNSIGNED_BYTE, GL_RGBA, GL_RGBA, 0, 1024 }, // I
		}
	}
};

/** cite from RiceVideo */
inline uint32_t CalculateDXT(uint32_t txl2words)
{
	if (txl2words == 0) return 1;
	else return (2048 + txl2words - 1) / txl2words;
}

static uint32_t glN64sizeBytes[4] = {0, 1, 2, 4};

inline uint32_t Txl2Words(uint32_t width, uint32_t size)
{
	if (size == 0)
		return max(1U, width / 16);
	else
		return max(1U, width * glN64sizeBytes[size] / 8);
}

inline uint32_t ReverseDXT(uint32_t val, uint32_t lrs, uint32_t width, uint32_t size)
{
	if (val == 0x800) return 1;

	int low = 2047 / val;
	if (CalculateDXT(low) > val)	low++;
	int high = 2047 / (val - 1);

	if (low == high)	return low;

	for (int i = low; i <= high; i++) {
		if (Txl2Words(width, size) == (uint32_t)i)
			return i;
	}

	return	(low + high) / 2;
}
/** end RiceVideo cite */

TextureCache & TextureCache::get() {
	static TextureCache cache;
	return cache;
}

void TextureCache::_initDummyTexture(CachedTexture * _pDummy)
{
	_pDummy->address = 0;
	_pDummy->clampS = 1;
	_pDummy->clampT = 1;
	_pDummy->clampWidth = 2;
	_pDummy->clampHeight = 2;
	_pDummy->crc = 0;
	_pDummy->format = 0;
	_pDummy->size = 0;
	_pDummy->frameBufferTexture = CachedTexture::fbNone;
	_pDummy->width = 2;
	_pDummy->height = 2;
	_pDummy->realWidth = 2;
	_pDummy->realHeight = 2;
	_pDummy->maskS = 0;
	_pDummy->maskT = 0;
	_pDummy->scaleS = 0.5f;
	_pDummy->scaleT = 0.5f;
	_pDummy->shiftScaleS = 1.0f;
	_pDummy->shiftScaleT = 1.0f;
	_pDummy->textureBytes = 2 * 2 * 4;
	_pDummy->tMem = 0;
}

void TextureCache::init()
{
	m_maxBytes = config.texture.maxBytes;
	m_curUnpackAlignment = 0;

	uint32_t dummyTexture[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

	m_pDummy = addFrameBufferTexture(); // we don't want to remove dummy texture
	_initDummyTexture(m_pDummy);

	glBindTexture(GL_TEXTURE_2D, m_pDummy->glName);
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, dummyTexture );

	m_cachedBytes = m_pDummy->textureBytes;
	activateDummy( 0 );
	activateDummy( 1 );
	current[0] = current[1] = NULL;

#ifdef GL_MULTISAMPLING_SUPPORT
	if (config.video.multisampling != 0) {
		m_pMSDummy = addFrameBufferTexture(); // we don't want to remove dummy texture
		_initDummyTexture(m_pMSDummy);

		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_pMSDummy->glName);

#if defined(HAVE_OPENGLES_3_1)
		glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, config.video.multisampling,
					GL_RGBA8, m_pMSDummy->realWidth, m_pMSDummy->realHeight, false);
#else
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, config.video.multisampling,
					GL_RGBA8, m_pMSDummy->realWidth, m_pMSDummy->realHeight, false);
#endif

		activateMSDummy(0);
		activateMSDummy(1);
	} else
#endif
	m_pMSDummy = NULL;
	assert(!isGLError());
}

void TextureCache::destroy()
{
	current[0] = current[1] = NULL;

	for (Textures::const_iterator cur = m_textures.cbegin(); cur != m_textures.cend(); ++cur)
		glDeleteTextures( 1, &cur->glName );
	m_textures.clear();
	m_lruTextureLocations.clear();

	for (FBTextures::const_iterator cur = m_fbTextures.cbegin(); cur != m_fbTextures.cend(); ++cur)
		glDeleteTextures( 1, &cur->second.glName );
	m_fbTextures.clear();

	m_cachedBytes = 0;
}

void TextureCache::_checkCacheSize()
{
	if (m_cachedBytes <= m_maxBytes)
		return;

	Textures::iterator iter = m_textures.end();
	do {
		--iter;
		CachedTexture& tex = *iter;
		m_cachedBytes -= tex.textureBytes;
		glDeleteTextures(1, &tex.glName);
		m_lruTextureLocations.erase(tex.crc);
	} while (m_cachedBytes > m_maxBytes && iter != m_textures.cbegin());
	m_textures.erase(iter, m_textures.end());
}

CachedTexture * TextureCache::_addTexture(uint32_t _crc32)
{
	if (m_curUnpackAlignment == 0)
		glGetIntegerv(GL_UNPACK_ALIGNMENT, &m_curUnpackAlignment);
	_checkCacheSize();
	GLuint glName;
	glGenTextures(1, &glName);
	m_textures.emplace_front(glName);
	Textures::iterator new_iter = m_textures.begin();
	new_iter->crc = _crc32;
	m_lruTextureLocations.insert(std::pair<uint32_t, Textures::iterator>(_crc32, new_iter));
	return &(*new_iter);
}

void TextureCache::removeFrameBufferTexture(CachedTexture * _pTexture)
{
	FBTextures::const_iterator iter = m_fbTextures.find(_pTexture->glName);
	assert(iter != m_fbTextures.cend());
	m_cachedBytes -= iter->second.textureBytes;
	glDeleteTextures( 1, &iter->second.glName );
	m_fbTextures.erase(iter);
}

CachedTexture * TextureCache::addFrameBufferTexture()
{
	_checkCacheSize();
	GLuint glName;
	glGenTextures(1, &glName);
	m_fbTextures.emplace(glName, glName);
	return &m_fbTextures.at(glName);
}

struct TileSizes
{
	uint32_t maskWidth, clampWidth, width, realWidth;
	uint32_t maskHeight, clampHeight, height, realHeight;
};

static
void _calcTileSizes(uint32_t _t, TileSizes & _sizes, gDPTile * _pLoadTile)
{
	gDPTile * pTile = _t < 2 ? gSP.textureTile[_t] : &gDP.tiles[_t];

	const TextureLoadParameters & loadParams = imageFormat[gDP.otherMode.textureLUT][pTile->size][pTile->format];
	const uint32_t maxTexels = loadParams.maxTexels;
	const uint32_t tileWidth = ((pTile->lrs - pTile->uls) & 0x03FF) + 1;
	const uint32_t tileHeight = ((pTile->lrt - pTile->ult) & 0x03FF) + 1;

	const bool bUseLoadSizes = _pLoadTile != NULL && _pLoadTile->loadType == LOADTYPE_TILE &&
		(pTile->tmem == _pLoadTile->tmem);

	uint32_t loadWidth = 0, loadHeight = 0;
	if (bUseLoadSizes) {
		loadWidth = ((_pLoadTile->lrs - _pLoadTile->uls) & 0x03FF) + 1;
		loadHeight = ((_pLoadTile->lrt - _pLoadTile->ult) & 0x03FF) + 1;
	}

	const uint32_t lineWidth = pTile->line << loadParams.lineShift;
	const uint32_t lineHeight = lineWidth != 0 ? min(maxTexels / lineWidth, tileHeight) : 0;

	uint32_t maskWidth = 1 << pTile->masks;
	uint32_t maskHeight = 1 << pTile->maskt;
	uint32_t width, height;

	gDPLoadTileInfo &info = gDP.loadInfo[pTile->tmem];
	if (info.loadType == LOADTYPE_TILE) {
		if (pTile->masks && ((maskWidth * maskHeight) <= maxTexels))
			width = maskWidth; // Use mask width if set and valid
		else {
			width = info.width;
			if (info.size > pTile->size)
				width <<= info.size - pTile->size;
		}
		if (pTile->maskt && ((maskWidth * maskHeight) <= maxTexels))
			height = maskHeight;
		else
			height = info.height;
	} else {
		if (pTile->masks && ((maskWidth * maskHeight) <= maxTexels))
			width = maskWidth; // Use mask width if set and valid
		else if ((tileWidth * tileHeight) <= maxTexels)
			width = tileWidth; // else use tile width if valid
		else
			width = lineWidth; // else use line-based width

		if (pTile->maskt && ((maskWidth * maskHeight) <= maxTexels))
			height = maskHeight;
		else if ((tileWidth * tileHeight) <= maxTexels)
			height = tileHeight;
		else
			height = lineHeight;
	}

	_sizes.clampWidth = (pTile->clamps && gDP.otherMode.cycleType != G_CYC_COPY) ? tileWidth : width;
	_sizes.clampHeight = (pTile->clampt && gDP.otherMode.cycleType != G_CYC_COPY) ? tileHeight : height;

	if (_sizes.clampWidth > 256)
		pTile->clamps = 0;
	if (_sizes.clampHeight > 256)
		pTile->clampt = 0;

	// Make sure masking is valid
	if (maskWidth > width) {
		pTile->masks = powof(width);
		maskWidth = 1 << pTile->masks;
	}

	if (maskHeight > height) {
		pTile->maskt = powof(height);
		maskHeight = 1 << pTile->maskt;
	}

	_sizes.maskWidth = maskWidth;
	_sizes.maskHeight = maskHeight;
	_sizes.width = width;
	_sizes.height = height;

	if (pTile->clamps != 0)
		_sizes.realWidth = _sizes.clampWidth;
	else if (pTile->masks != 0)
		_sizes.realWidth = _sizes.maskWidth;
	else
		_sizes.realWidth = _sizes.width;

	if (pTile->clampt != 0)
		_sizes.realHeight = _sizes.clampHeight;
	else if (pTile->maskt != 0)
		_sizes.realHeight = _sizes.maskHeight;
	else
		_sizes.realHeight = _sizes.height;

	if (gSP.texture.level > gSP.texture.tile) {
		_sizes.realWidth = pow2(_sizes.realWidth);
		_sizes.realHeight = pow2(_sizes.realHeight);
	}
}

inline
void _updateCachedTexture(const GHQTexInfo & _info, CachedTexture *_pTexture)
{
	_pTexture->textureBytes = _info.width * _info.height;
	switch (_info.format) {
		case GL_RGB:
		case GL_RGBA4:
		case GL_RGB5_A1:
		_pTexture->textureBytes <<= 1;
		break;
		default:
		_pTexture->textureBytes <<= 2;
	}
	_pTexture->realWidth = _info.width;
	_pTexture->realHeight = _info.height;
	/*
	_pTexture->scaleS = 1.0f / (float)(_pTexture->realWidth);
	_pTexture->scaleT = 1.0f / (float)(_pTexture->realHeight);
	*/
}

bool TextureCache::_loadHiresBackground(CachedTexture *_pTexture)
{
	if (!TFH.isInited())
		return false;

	uint8_t * addr = (uint8_t*)(RDRAM + gSP.bgImage.address);
	int tile_width = gSP.bgImage.width;
	int tile_height = gSP.bgImage.height;
	int bpl = tile_width << gSP.bgImage.size >> 1;

	uint8_t * paladdr = NULL;
	uint16_t * palette = NULL;
	if ((gSP.bgImage.size < G_IM_SIZ_16b) && (gDP.otherMode.textureLUT != G_TT_NONE || gSP.bgImage.format == G_IM_FMT_CI)) {
		if (gSP.bgImage.size == G_IM_SIZ_8b)
			paladdr = (uint8_t*)(gDP.TexFilterPalette);
		else if (config.textureFilter.txHresAltCRC)
			paladdr = (uint8_t*)(gDP.TexFilterPalette + (gSP.bgImage.palette << 5));
		else
			paladdr = (uint8_t*)(gDP.TexFilterPalette + (gSP.bgImage.palette << 4));
		// TODO: fix palette load
		//			palette = (rdp.pal_8 + (gSP.textureTile[_t]->palette << 4));
	}

	uint64_t ricecrc = txfilter_checksum(addr, tile_width,
						tile_height, (unsigned short)(gSP.bgImage.format << 8 | gSP.bgImage.size),
						bpl, paladdr);
	GHQTexInfo ghqTexInfo;
	if (txfilter_hirestex(_pTexture->crc, ricecrc, palette, &ghqTexInfo)) {
		glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format,
			ghqTexInfo.width, ghqTexInfo.height, 0, ghqTexInfo.texture_format,
			ghqTexInfo.pixel_type, ghqTexInfo.data);
		assert(!isGLError());
		_updateCachedTexture(ghqTexInfo, _pTexture);
		return true;
	}
	return false;
}

void TextureCache::_loadBackground(CachedTexture *pTexture)
{
	if (_loadHiresBackground(pTexture))
		return;

	uint32_t *pDest;

	uint8_t *pSwapped, *pSrc;
	uint32_t numBytes, bpl;
	uint32_t x, y, j, tx, ty;
	uint16_t clampSClamp;
	uint16_t clampTClamp;
	GetTexelFunc GetTexel;
	GLuint glInternalFormat;
	GLenum glType;

	const TextureLoadParameters & loadParams = imageFormat[pTexture->format == 2 ? G_TT_RGBA16 : G_TT_NONE][pTexture->size][pTexture->format];
	if (loadParams.autoFormat == GL_RGBA) {
		pTexture->textureBytes = (pTexture->realWidth * pTexture->realHeight) << 2;
		GetTexel = loadParams.Get32;
		glInternalFormat = loadParams.glInternalFormat32;
		glType = loadParams.glType32;
	} else {
		pTexture->textureBytes = (pTexture->realWidth * pTexture->realHeight) << 1;
		GetTexel = loadParams.Get16;
		glInternalFormat = loadParams.glInternalFormat16;
		glType = loadParams.glType16;
	}

	bpl = gSP.bgImage.width << gSP.bgImage.size >> 1;
	numBytes = bpl * gSP.bgImage.height;
	pSwapped = (uint8_t*)malloc(numBytes);
	assert(pSwapped != NULL);
	UnswapCopyWrap(RDRAM, gSP.bgImage.address, pSwapped, 0, RDRAMSize, numBytes);
	pDest = (uint32_t*)malloc(pTexture->textureBytes);
	assert(pDest != NULL);

	clampSClamp = pTexture->width - 1;
	clampTClamp = pTexture->height - 1;

	j = 0;
	for (y = 0; y < pTexture->realHeight; y++) {
		ty = min(y, (uint32_t)clampTClamp);

		pSrc = &pSwapped[bpl * ty];

		for (x = 0; x < pTexture->realWidth; x++) {
			tx = min(x, (uint32_t)clampSClamp);

			if (glInternalFormat == GL_RGBA)
				((uint32_t*)pDest)[j++] = GetTexel((uint64_t*)pSrc, tx, 0, pTexture->palette);
			else
				((uint16_t*)pDest)[j++] = GetTexel((uint64_t*)pSrc, tx, 0, pTexture->palette);
		}
	}

	bool bLoaded = false;
	if ((config.textureFilter.txEnhancementMode | config.textureFilter.txFilterMode) != 0 &&
			config.textureFilter.txFilterIgnoreBG == 0 &&
			TFH.isInited()) {
		GHQTexInfo ghqTexInfo;
		if (txfilter_filter((uint8_t*)pDest, pTexture->realWidth, pTexture->realHeight,
				glInternalFormat, (uint64)pTexture->crc, &ghqTexInfo) != 0 &&
				ghqTexInfo.data != NULL) {
			if (ghqTexInfo.width % 2 != 0 &&
					ghqTexInfo.format != GL_RGBA &&
					m_curUnpackAlignment > 1)
				glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
			glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format,
					ghqTexInfo.width, ghqTexInfo.height, 0,
					ghqTexInfo.texture_format, ghqTexInfo.pixel_type,
					ghqTexInfo.data);
			_updateCachedTexture(ghqTexInfo, pTexture);
			bLoaded = true;
		}
	}
	if (!bLoaded) {
		if (pTexture->realWidth % 2 != 0 && glInternalFormat != GL_RGBA)
			glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
#ifdef HAVE_OPENGLES2
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTexture->realWidth,
				pTexture->realHeight, 0, GL_RGBA, glType, pDest);
#else
		glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, pTexture->realWidth,
				pTexture->realHeight, 0, GL_RGBA, glType, pDest);
#endif
	}
	if (m_curUnpackAlignment > 1)
		glPixelStorei(GL_UNPACK_ALIGNMENT, m_curUnpackAlignment);
	free(pDest);
}

bool TextureCache::_loadHiresTexture(uint32_t _tile, CachedTexture *_pTexture, uint64_t & _ricecrc)
{
	if (config.textureFilter.txHiresEnable == 0 || !TFH.isInited())
		return false;

	gDPLoadTileInfo & info = gDP.loadInfo[_pTexture->tMem];

	int bpl;
	uint8_t * addr = (uint8_t*)(RDRAM + info.texAddress);
	int tile_width = _pTexture->width;
	int tile_height = _pTexture->height;
	if (info.loadType == LOADTYPE_TILE) {
		bpl = info.texWidth << info.size >> 1;
		addr += (info.ult * bpl) + (((info.uls << info.size) + 1) >> 1);
	}
	else {
		if (gSP.textureTile[_tile]->size == G_IM_SIZ_32b)
			bpl = gSP.textureTile[_tile]->line << 4;
		else if (info.dxt == 0)
			bpl = gSP.textureTile[_tile]->line << 3;
		else {
			uint32_t dxt = info.dxt;
			if (dxt > 1)
				dxt = ReverseDXT(dxt, info.width, _pTexture->width, _pTexture->size);
			bpl = dxt << 3;
		}
	}

	uint8_t * paladdr = NULL;
	uint16_t * palette = NULL;
	if ((_pTexture->size < G_IM_SIZ_16b) && (gDP.otherMode.textureLUT != G_TT_NONE || _pTexture->format == G_IM_FMT_CI)) {
		if (_pTexture->size == G_IM_SIZ_8b)
			paladdr = (uint8_t*)(gDP.TexFilterPalette);
		else if (config.textureFilter.txHresAltCRC)
			paladdr = (uint8_t*)(gDP.TexFilterPalette + (_pTexture->palette << 5));
		else
			paladdr = (uint8_t*)(gDP.TexFilterPalette + (_pTexture->palette << 4));
		// TODO: fix palette load
		//			palette = (rdp.pal_8 + (gSP.textureTile[_t]->palette << 4));
	}

	_ricecrc = txfilter_checksum(addr, tile_width, tile_height, (unsigned short)(_pTexture->format << 8 | _pTexture->size), bpl, paladdr);
	GHQTexInfo ghqTexInfo;
	if (txfilter_hirestex(_pTexture->crc, _ricecrc, palette, &ghqTexInfo)) {
#ifdef HAVE_OPENGLES2
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ghqTexInfo.width, ghqTexInfo.height, 0, GL_RGBA, ghqTexInfo.pixel_type, ghqTexInfo.data);
#else
		glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format, ghqTexInfo.width, ghqTexInfo.height, 0, ghqTexInfo.texture_format, ghqTexInfo.pixel_type, ghqTexInfo.data);
#endif
		assert(!isGLError());
		_updateCachedTexture(ghqTexInfo, _pTexture);
		return true;
	}

	return false;
}

/*
 * Worker function for _load
*/
void TextureCache::_getTextureDestData(CachedTexture& tmptex,
						uint32_t* pDest,
						GLuint glInternalFormat,
						GetTexelFunc GetTexel,
						uint16_t* pLine)
{
	uint16_t mirrorSBit, maskSMask, clampSClamp;
	uint16_t mirrorTBit, maskTMask, clampTClamp;
	uint16_t x, y, i, j, tx, ty;
	uint64_t *pSrc;
	if (tmptex.maskS > 0) {
		clampSClamp = tmptex.clampS ? tmptex.clampWidth - 1 : (tmptex.mirrorS ? (tmptex.width << 1) - 1 : tmptex.width - 1);
		maskSMask = (1 << tmptex.maskS) - 1;
		mirrorSBit = (tmptex.mirrorS != 0 || tmptex.realWidth/tmptex.width == 2) ? 1 << tmptex.maskS : 0;
	} else {
		clampSClamp = min(tmptex.clampWidth, tmptex.width) - 1;
		maskSMask = 0xFFFF;
		mirrorSBit = 0x0000;
	}

	if (tmptex.maskT > 0) {
		clampTClamp = tmptex.clampT ? tmptex.clampHeight - 1 : (tmptex.mirrorT ? (tmptex.height << 1) - 1 : tmptex.height - 1);
		maskTMask = (1 << tmptex.maskT) - 1;
		mirrorTBit = (tmptex.mirrorT != 0 || tmptex.realHeight/tmptex.height == 2) ? 1 << tmptex.maskT : 0;
	} else {
		clampTClamp = min(tmptex.clampHeight, tmptex.height) - 1;
		maskTMask = 0xFFFF;
		mirrorTBit = 0x0000;
	}

	if (tmptex.size == G_IM_SIZ_32b) {
		const uint16_t * tmem16 = (uint16_t*)TMEM;
		const uint32_t tbase = tmptex.tMem << 2;

		int wid_64 = (tmptex.clampWidth) << 2;
		if (wid_64 & 15) {
			wid_64 += 16;
		}
		wid_64 &= 0xFFFFFFF0;
		wid_64 >>= 3;
		int line32 = tmptex.line << 1;
		line32 = (line32 - wid_64) << 3;
		if (wid_64 < 1) {
			wid_64 = 1;
		}
		int width = wid_64 << 1;
		line32 = width + (line32 >> 2);

		uint16_t gr, ab;

		j = 0;
		for (y = 0; y < tmptex.realHeight; ++y) {
			ty = min(y, clampTClamp) & maskTMask;
			if (y & mirrorTBit) {
				ty ^= maskTMask;
			}

			uint32_t tline = tbase + line32 * ty;
			uint32_t xorval = (ty & 1) ? 3 : 1;

			for (x = 0; x < tmptex.realWidth; ++x) {
				tx = min(x, clampSClamp) & maskSMask;
				if (x & mirrorSBit) {
					tx ^= maskSMask;
				}

				uint32_t taddr = ((tline + tx) ^ xorval) & 0x3ff;
				gr = swapword(tmem16[taddr]);
				ab = swapword(tmem16[taddr | 0x400]);
				pDest[j++] = (ab << 16) | gr;
			}
		}
	} else if (tmptex.format == G_IM_FMT_YUV) {
		j = 0;
		*pLine <<= 1;
		for (y = 0; y < tmptex.realHeight; ++y) {
			pSrc = &TMEM[tmptex.tMem] + *pLine * y;
			for (x = 0; x < tmptex.realWidth / 2; x++) {
				if (glInternalFormat == GL_RGBA) {
					GetYUV_RGBA8888(pSrc, pDest + j, x);
				} else {
					GetYUV_RGBA4444(pSrc, (uint16_t*)pDest + j, x);
				}
				j += 2;
			}
		}
	} else {
		j = 0;
      const uint32_t tMemMask = gDP.otherMode.textureLUT == G_TT_NONE ? 0x1FF : 0xFF;
		for (y = 0; y < tmptex.realHeight; ++y) {
			ty = min(y, clampTClamp) & maskTMask;

			if (y & mirrorTBit)
			ty ^= maskTMask;

         pSrc = &TMEM[(tmptex.tMem + *pLine * ty) & tMemMask];

			i = (ty & 1) << 1;
			for (x = 0; x < tmptex.realWidth; ++x) {
				tx = min(x, clampSClamp) & maskSMask;

				if (x & mirrorSBit) {
					tx ^= maskSMask;
				}

				if (glInternalFormat == GL_RGBA) {
					pDest[j++] = GetTexel(pSrc, tx, i, tmptex.palette);
				} else {
					((uint16_t*)pDest)[j++] = GetTexel(pSrc, tx, i, tmptex.palette);
				}
			}
		}
	}
}

void TextureCache::_load(uint32_t _tile, CachedTexture *_pTexture)
{
	uint64_t ricecrc = 0;
	if (_loadHiresTexture(_tile, _pTexture, ricecrc))
		return;

	uint32_t *pDest;

	uint16_t line;
	GetTexelFunc GetTexel;
	GLuint glInternalFormat;
	GLenum glType;
	uint32_t sizeShift;

	const TextureLoadParameters & loadParams = imageFormat[gDP.otherMode.textureLUT][_pTexture->size][_pTexture->format];
	if (loadParams.autoFormat == GL_RGBA) {
		sizeShift = 2;
		_pTexture->textureBytes = (_pTexture->realWidth * _pTexture->realHeight) << sizeShift;
		GetTexel = loadParams.Get32;
		glInternalFormat = loadParams.glInternalFormat32;
		glType = loadParams.glType32;
	} else {
		sizeShift = 1;
		_pTexture->textureBytes = (_pTexture->realWidth * _pTexture->realHeight) << sizeShift;
		GetTexel = loadParams.Get16;
		glInternalFormat = loadParams.glInternalFormat16;
		glType = loadParams.glType16;
	}

	pDest = (uint32_t*)malloc(_pTexture->textureBytes);
	assert(pDest != NULL);

	GLint mipLevel = 0, maxLevel = 0;
#ifndef HAVE_OPENGLES2
	if (config.generalEmulation.enableLOD != 0 && gSP.texture.level > gSP.texture.tile + 1)
		maxLevel = _tile == 0 ? 0 : gSP.texture.level - gSP.texture.tile - 1;
#endif

	_pTexture->max_level = maxLevel;

	CachedTexture tmptex(0);
	memcpy(&tmptex, _pTexture, sizeof(CachedTexture));

	line = tmptex.line;

	while (true) {
		_getTextureDestData(tmptex, pDest, glInternalFormat, GetTexel, &line);

		bool bLoaded = false;
		if (m_toggleDumpTex &&
				config.textureFilter.txHiresEnable != 0 &&
				config.textureFilter.txDump != 0) {
			txfilter_dmptx((uint8_t*)pDest, tmptex.realWidth, tmptex.realHeight,
					tmptex.realWidth, glInternalFormat,
					(unsigned short)(_pTexture->format << 8 | _pTexture->size),
					ricecrc);
		}
		else if ((config.textureFilter.txEnhancementMode | config.textureFilter.txFilterMode) != 0 &&
				maxLevel == 0 &&
				(config.textureFilter.txFilterIgnoreBG == 0 || (__RSP.cmd != G_TEXRECT && __RSP.cmd != G_TEXRECTFLIP)) &&
				TFH.isInited())
		{
			GHQTexInfo ghqTexInfo;
			if (txfilter_filter((uint8_t*)pDest, tmptex.realWidth, tmptex.realHeight,
							glInternalFormat, (uint64)_pTexture->crc,
							&ghqTexInfo) != 0 && ghqTexInfo.data != NULL) {
#ifdef HAVE_OPENGLES2
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
						ghqTexInfo.width, ghqTexInfo.height,
						0, GL_RGBA, ghqTexInfo.pixel_type,
						ghqTexInfo.data);
#else
				glTexImage2D(GL_TEXTURE_2D, 0, ghqTexInfo.format,
						ghqTexInfo.width, ghqTexInfo.height,
						0, ghqTexInfo.texture_format, ghqTexInfo.pixel_type,
						ghqTexInfo.data);
#endif
				_updateCachedTexture(ghqTexInfo, _pTexture);
				bLoaded = true;
			}
		}
		if (!bLoaded) {
			if (tmptex.realWidth % 2 != 0 &&
					glInternalFormat != GL_RGBA &&
					m_curUnpackAlignment > 1)
				glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
#ifdef HAVE_OPENGLES2
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tmptex.realWidth,
					tmptex.realHeight, 0, GL_RGBA, glType, pDest);
#else
			glTexImage2D(GL_TEXTURE_2D, mipLevel, glInternalFormat, tmptex.realWidth,
					tmptex.realHeight, 0, GL_RGBA, glType, pDest);
#endif
		}
		if (mipLevel == maxLevel)
			break;
		++mipLevel;
		const uint32_t tileMipLevel = gSP.texture.tile + mipLevel + 1;
		gDPTile & mipTile = gDP.tiles[tileMipLevel];
		line = mipTile.line;
		tmptex.tMem = mipTile.tmem;
		tmptex.palette = mipTile.palette;
		tmptex.maskS = mipTile.masks;
		tmptex.maskT = mipTile.maskt;
		TileSizes sizes;
		_calcTileSizes(tileMipLevel, sizes, NULL);
		tmptex.width = sizes.width;
		tmptex.clampWidth = sizes.clampWidth;
		tmptex.height = sizes.height;
		tmptex.clampHeight = sizes.clampHeight;
		// Insure mip-map levels size consistency.
		if (tmptex.realWidth > 1)
			tmptex.realWidth >>= 1;
		if (tmptex.realHeight > 1)
			tmptex.realHeight >>= 1;
		_pTexture->textureBytes += (tmptex.realWidth * tmptex.realHeight) << sizeShift;
	}
	if (m_curUnpackAlignment > 1)
		glPixelStorei(GL_UNPACK_ALIGNMENT, m_curUnpackAlignment);
	free(pDest);
}

struct TextureParams
{
	uint16_t width;
	uint16_t height;
	uint16_t clampWidth;
	uint16_t clampHeight;
	uint8_t maskS;
	uint8_t maskT;
	uint8_t mirrorS;
	uint8_t mirrorT;
	uint8_t clampS;
	uint8_t clampT;
	uint8_t format;
	uint8_t size;
};

static
uint32_t _calculateCRC(uint32_t t, const TextureParams & _params)
{
	const uint32_t line = gSP.textureTile[t]->line;
	const uint32_t lineBytes = line << 3;

	const uint64_t *src = (uint64_t*)&TMEM[gSP.textureTile[t]->tmem];
	uint32_t crc = 0xFFFFFFFF;
	crc = CRC_Calculate(crc, src, _params.height*lineBytes);

	if (gSP.textureTile[t]->size == G_IM_SIZ_32b) {
		src = (uint64_t*)&TMEM[gSP.textureTile[t]->tmem + 256];
		crc = CRC_Calculate(crc, src, _params.height*lineBytes);
	}

	if (gDP.otherMode.textureLUT != G_TT_NONE || gSP.textureTile[t]->format == G_IM_FMT_CI) {
		if (gSP.textureTile[t]->size == G_IM_SIZ_4b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC16[gSP.textureTile[t]->palette], 4 );
		else if (gSP.textureTile[t]->size == G_IM_SIZ_8b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC256, 4 );
	}

	crc = CRC_Calculate(crc, &_params, sizeof(_params));

	return crc;
}

void TextureCache::activateTexture(uint32_t _t, CachedTexture *_pTexture)
{
#ifdef GL_MULTISAMPLING_SUPPORT
	if (config.video.multisampling > 0 && _pTexture->frameBufferTexture == CachedTexture::fbMultiSample) {
		glActiveTexture(GL_TEXTURE0 + g_MSTex0Index + _t);
		// Bind the cached texture
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, _pTexture->glName);
	} else
#endif
	{
		glActiveTexture(GL_TEXTURE0 + _t);
		// Bind the cached texture
		glBindTexture(GL_TEXTURE_2D, _pTexture->glName);
	}

	const bool bUseBilinear = (gDP.otherMode.textureFilter | (gSP.objRendermode&G_OBJRM_BILERP)) != 0;
	const bool bUseLOD = currentCombiner()->usesLOD();
	const GLint texLevel = bUseLOD ? _pTexture->max_level : 0;

#ifndef HAVE_OPENGLES2
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, texLevel);
#endif
	if (config.texture.bilinearMode == BILINEAR_STANDARD) {
		if (bUseBilinear) {
			if (texLevel > 0)
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
			else
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		} else {
			if (texLevel > 0)
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
			else
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
	} else { // 3 point filter
		if (texLevel > 0) { // Apply standard bilinear to mipmap textures
			if (bUseBilinear) {
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			} else {
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			}
		} else if (bUseBilinear && config.generalEmulation.enableLOD != 0 && bUseLOD) { // Apply standard bilinear to first tile of mipmap texture
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		} else { // Don't use texture filter. Texture will be filtered by 3 point filter shader
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
	}


	// Set clamping modes
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _pTexture->clampS ? GL_CLAMP_TO_EDGE : _pTexture->mirrorS ? GL_MIRRORED_REPEAT : GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _pTexture->clampT ? GL_CLAMP_TO_EDGE : _pTexture->mirrorT ? GL_MIRRORED_REPEAT : GL_REPEAT);

	if (video().getRender().getRenderState() == OGLRender::rsTriangle && config.texture.maxAnisotropyF > 0.0f)
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, config.texture.maxAnisotropyF);

	_pTexture->lastDList = video().getBuffersSwapCount();

	current[_t] = _pTexture;
}

void TextureCache::activateDummy(uint32_t _t)
{
	glActiveTexture( GL_TEXTURE0 + _t );

	glBindTexture( GL_TEXTURE_2D, m_pDummy->glName );

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}

void TextureCache::activateMSDummy(uint32_t _t)
{
#ifdef GL_MULTISAMPLING_SUPPORT
	glActiveTexture(GL_TEXTURE0 + g_MSTex0Index + _t);

	glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_pMSDummy->glName);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
#endif
}

void TextureCache::_updateBackground()
{
	uint32_t numBytes = gSP.bgImage.width * gSP.bgImage.height << gSP.bgImage.size >> 1;
	uint32_t crc;

	crc = CRC_Calculate( 0xFFFFFFFF, &RDRAM[gSP.bgImage.address], numBytes );

	if (gDP.otherMode.textureLUT != G_TT_NONE || gSP.bgImage.format == G_IM_FMT_CI) {
		if (gSP.bgImage.size == G_IM_SIZ_4b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC16[gSP.bgImage.palette], 4 );
		else if (gSP.bgImage.size == G_IM_SIZ_8b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC256, 4 );
	}

	uint32_t params[4] = {gSP.bgImage.width, gSP.bgImage.height, gSP.bgImage.format, gSP.bgImage.size};
	crc = CRC_Calculate(crc, params, sizeof(uint32_t)*4);

	Texture_Locations::iterator locations_iter = m_lruTextureLocations.find(crc);
	if (locations_iter != m_lruTextureLocations.end()) {
		Textures::iterator iter = locations_iter->second;
		CachedTexture & current = *iter;
		m_textures.splice(m_textures.begin(), m_textures, iter);

		assert(current.width == gSP.bgImage.width);
		assert(current.height == gSP.bgImage.height);
		assert(current.format == gSP.bgImage.format);
		assert(current.size == gSP.bgImage.size);

		activateTexture(0, &current);
		m_hits++;
		return;
	}

	m_misses++;

	glActiveTexture( GL_TEXTURE0 );
	CachedTexture * pCurrent = _addTexture(crc);

	glBindTexture( GL_TEXTURE_2D, pCurrent->glName );

	pCurrent->address = gSP.bgImage.address;

	pCurrent->format = gSP.bgImage.format;
	pCurrent->size = gSP.bgImage.size;

	pCurrent->width = gSP.bgImage.width;
	pCurrent->height = gSP.bgImage.height;

	pCurrent->clampWidth = gSP.bgImage.width;
	pCurrent->clampHeight = gSP.bgImage.height;
	pCurrent->palette = gSP.bgImage.palette;
	pCurrent->maskS = 0;
	pCurrent->maskT = 0;
	pCurrent->mirrorS = 0;
	pCurrent->mirrorT = 0;
	pCurrent->clampS = 0;
	pCurrent->clampT = 0;
	pCurrent->line = 0;
	pCurrent->tMem = 0;
	pCurrent->lastDList = video().getBuffersSwapCount();
	pCurrent->frameBufferTexture = CachedTexture::fbNone;

	pCurrent->realWidth = gSP.bgImage.width;
	pCurrent->realHeight = gSP.bgImage.height;

	pCurrent->scaleS = 1.0f / (float)(pCurrent->realWidth);
	pCurrent->scaleT = 1.0f / (float)(pCurrent->realHeight);

	pCurrent->shiftScaleS = 1.0f;
	pCurrent->shiftScaleT = 1.0f;

	pCurrent->offsetS = 0.5f;
	pCurrent->offsetT = 0.5f;

	_loadBackground(pCurrent);
	activateTexture(0, pCurrent);

	m_cachedBytes += pCurrent->textureBytes;
	current[0] = pCurrent;
}

void TextureCache::_clear()
{
	current[0] = current[1] = NULL;

	for (Textures::const_iterator cur = m_textures.cbegin(); cur != m_textures.cend(); ++cur) {
		m_cachedBytes -= cur->textureBytes;
		glDeleteTextures(1, &cur->glName);
	}
	m_textures.clear();
	m_lruTextureLocations.clear();
}

void TextureCache::update(uint32_t _t)
{
	if (config.textureFilter.txHiresEnable != 0 && config.textureFilter.txDump != 0) {
		/* Force reload hi-res textures. Useful for texture artists */
#if 0
		if (isKeyPressed(G64_VK_R, 0x0001)) {
			if (txfilter_reloadhirestex())
				_clear();
		}
#endif

#if 0
		/* Turn on texture dump */
		else if (isKeyPressed(G64_VK_D, 0x0001))
      {
			m_toggleDumpTex = !m_toggleDumpTex;
			if (m_toggleDumpTex)
         {
				displayLoadProgress(L"Texture dump - ON\n");
				_clear();
				std::this_thread::sleep_for(std::chrono::seconds(1));
			}
			else
         {
				displayLoadProgress(L"Texture dump - OFF\n");
				std::this_thread::sleep_for(std::chrono::seconds(1));
			}
		}
#endif
	}


	switch(gSP.textureTile[_t]->textureMode) {
	case TEXTUREMODE_BGIMAGE:
		_updateBackground();
		return;
	case TEXTUREMODE_FRAMEBUFFER:
		FrameBuffer_ActivateBufferTexture( _t, gSP.textureTile[_t]->frameBuffer );
		return;
	case TEXTUREMODE_FRAMEBUFFER_BG:
		FrameBuffer_ActivateBufferTextureBG( _t, gSP.textureTile[_t]->frameBuffer );
		return;
	}

	if (gDP.otherMode.textureLOD == G_TL_LOD && gSP.texture.level == gSP.texture.tile && _t == 1) {
		current[1] = current[0];
		activateTexture(_t, current[_t]);
		return;
	}

	if (gSP.texture.tile == 7 &&
			_t == 0 &&
			gSP.textureTile[0] == gDP.loadTile &&
			gDP.loadTile->loadType == LOADTYPE_BLOCK &&
			gSP.textureTile[0]->tmem == gSP.textureTile[1]->tmem)
		gSP.textureTile[0] = gSP.textureTile[1];

	TileSizes sizes;
	_calcTileSizes(_t, sizes, gDP.loadTile);

	uint32_t crc;
	{
	TextureParams params;
	params.width = sizes.width;
	params.height = sizes.height;
	params.clampWidth = sizes.clampWidth;
	params.clampHeight = sizes.clampHeight;
	params.maskS = gSP.textureTile[_t]->masks;
	params.maskT = gSP.textureTile[_t]->maskt;
	params.mirrorS = gSP.textureTile[_t]->mirrors;
	params.mirrorT = gSP.textureTile[_t]->mirrort;
	params.clampS = gSP.textureTile[_t]->clamps;
	params.clampT = gSP.textureTile[_t]->clampt;
	params.format = gSP.textureTile[_t]->format;
	params.size = gSP.textureTile[_t]->size;

	crc = _calculateCRC( _t, params );
	}

	if (current[_t] != NULL && current[_t]->crc == crc) {
		activateTexture(_t, current[_t]);
		return;
	}

	Texture_Locations::iterator locations_iter = m_lruTextureLocations.find(crc);
	if (locations_iter != m_lruTextureLocations.end()) {
		Textures::iterator iter = locations_iter->second;
		CachedTexture & current = *iter;
		m_textures.splice(m_textures.begin(), m_textures, iter);

		assert(current.width == sizes.width);
		assert(current.height == sizes.height);
		assert(current.clampWidth == sizes.clampWidth);
		assert(current.clampHeight == sizes.clampHeight);
		assert(current.maskS == gSP.textureTile[_t]->masks);
		assert(current.maskT == gSP.textureTile[_t]->maskt);
		assert(current.mirrorS == gSP.textureTile[_t]->mirrors);
		assert(current.mirrorT == gSP.textureTile[_t]->mirrort);
		assert(current.clampS == gSP.textureTile[_t]->clamps);
		assert(current.clampT == gSP.textureTile[_t]->clampt);
		assert(current.format == gSP.textureTile[_t]->format);
		assert(current.size == gSP.textureTile[_t]->size);

		activateTexture(_t, &current);
		m_hits++;
		return;
	}

	m_misses++;

	glActiveTexture( GL_TEXTURE0 + _t );

	CachedTexture * pCurrent = _addTexture(crc);

	glBindTexture( GL_TEXTURE_2D, pCurrent->glName );

	pCurrent->address = gDP.loadInfo[gSP.textureTile[_t]->tmem].texAddress;

	pCurrent->format = gSP.textureTile[_t]->format;
	pCurrent->size = gSP.textureTile[_t]->size;

	pCurrent->width = sizes.width;
	pCurrent->height = sizes.height;

	pCurrent->clampWidth = sizes.clampWidth;
	pCurrent->clampHeight = sizes.clampHeight;

	pCurrent->palette = gSP.textureTile[_t]->palette;
/*	pCurrent->fulS = gSP.textureTile[t]->fulS;
	pCurrent->fulT = gSP.textureTile[t]->fulT;
	pCurrent->ulS = gSP.textureTile[t]->ulS;
	pCurrent->ulT = gSP.textureTile[t]->ulT;
	pCurrent->lrS = gSP.textureTile[t]->lrS;
	pCurrent->lrT = gSP.textureTile[t]->lrT;*/
	pCurrent->maskS = gSP.textureTile[_t]->masks;
	pCurrent->maskT = gSP.textureTile[_t]->maskt;
	pCurrent->mirrorS = gSP.textureTile[_t]->mirrors;
	pCurrent->mirrorT = gSP.textureTile[_t]->mirrort;
	pCurrent->clampS = gSP.textureTile[_t]->clamps;
	pCurrent->clampT = gSP.textureTile[_t]->clampt;
	pCurrent->line = gSP.textureTile[_t]->line;
	pCurrent->tMem = gSP.textureTile[_t]->tmem;
	pCurrent->lastDList = video().getBuffersSwapCount();
	pCurrent->frameBufferTexture = CachedTexture::fbNone;

	pCurrent->realWidth = sizes.realWidth;
	pCurrent->realHeight = sizes.realHeight;

	pCurrent->scaleS = 1.0f / (float)(pCurrent->realWidth);
	pCurrent->scaleT = 1.0f / (float)(pCurrent->realHeight);

	pCurrent->offsetS = 0.5f;
	pCurrent->offsetT = 0.5f;

	_load(_t, pCurrent);
	activateTexture( _t, pCurrent );

	m_cachedBytes += pCurrent->textureBytes;
	current[_t] = pCurrent;
}

void getTextureShiftScale(uint32_t t, const TextureCache & cache, float & shiftScaleS, float & shiftScaleT)
{
	if (gSP.textureTile[t]->textureMode != TEXTUREMODE_NORMAL) {
		shiftScaleS = cache.current[t]->shiftScaleS;
		shiftScaleT = cache.current[t]->shiftScaleT;
		return;
	}

	if (gDP.otherMode.textureLOD == G_TL_LOD && gSP.texture.level == gSP.texture.tile)
		t = 0;

	if (gSP.textureTile[t]->shifts > 10)
		shiftScaleS = (float)(1 << (16 - gSP.textureTile[t]->shifts));
	else if (gSP.textureTile[t]->shifts > 0)
		shiftScaleS /= (float)(1 << gSP.textureTile[t]->shifts);

	if (gSP.textureTile[t]->shiftt > 10)
		shiftScaleT = (float)(1 << (16 - gSP.textureTile[t]->shiftt));
	else if (gSP.textureTile[t]->shiftt > 0)
		shiftScaleT /= (float)(1 << gSP.textureTile[t]->shiftt);
}
