#include <assert.h>
#include <memory.h>
#include <algorithm>
#include <thread>         // std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
#include "Platform.h"
#include "Textures.h"
#include "GBI.h"
#include "RSP.h"
#include "RDP.h"
#include "gDP.h"
#include "gSP.h"
#include "N64.h"
#include "convert.h"
#include "FrameBuffer.h"
#include "Config.h"
#include "GLideNHQ/TxFilterExport.h"
#include "TextureFilterHandler.h"
#include "DisplayLoadProgress.h"
#include "Graphics/Context.h"
#include "Graphics/Parameters.h"
#include "DisplayWindow.h"
#include <mupen64plus-next_common.h>

using namespace std;
using namespace graphics;

u32 GetNone(u16 offset, u16 x, u16 i, u8 palette)
{
	return 0x00000000;
}

inline u8 Get4BitPaletteColor(u16 offset, u16 x, u16 i)
{
	u8* tmem8 = reinterpret_cast<u8*>(TMEM);
	return tmem8[((offset << 3) + ((x >> 1) ^ (i << 1))) & 0xFFF];
}

u32 GetCI4_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return CI4_RGBA8888((x & 1) ? (palette << 4) | (color4B & 0x0F) : (palette << 4) | (color4B >> 4));
}

u32 GetCI4_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return CI4_RGBA4444((x & 1) ? (palette << 4) | (color4B & 0x0F) : (palette << 4) | (color4B >> 4));
}

u32 GetCI4IA_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);

	if (x & 1)
		return IA88_RGBA4444(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B & 0x0F)) & 0x1FF] & 0xFFFF));
	else
		return IA88_RGBA4444(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B >> 4)) & 0x1FF] & 0xFFFF));
}

u32 GetCI4IA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);

	if (x & 1)
		return IA88_RGBA8888(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B & 0x0F)) & 0x1FF] & 0xFFFF));
	else
		return IA88_RGBA8888(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B >> 4)) & 0x1FF] & 0xFFFF));
}

u32 GetCI4RGBA_RGBA5551(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);

	if (x & 1)
		return RGBA5551_RGBA5551(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B & 0x0F)) & 0x1FF] & 0xFFFF));
	else
		return RGBA5551_RGBA5551(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B >> 4)) & 0x1FF] & 0xFFFF));
}

u32 GetCI4RGBA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);

	if (x & 1)
		return RGBA5551_RGBA8888(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B & 0x0F)) & 0x1FF] & 0xFFFF));
	else
		return RGBA5551_RGBA8888(static_cast<u16>(TMEM[(0x100 + (palette << 4) + (color4B >> 4)) & 0x1FF] & 0xFFFF));
}

u32 GetIA31_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return IA31_RGBA8888((x & 1) ? (color4B & 0x0F) : (color4B >> 4));
}

u32 GetIA31_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return IA31_RGBA4444((x & 1) ? (color4B & 0x0F) : (color4B >> 4));
}

u32 GetI4_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return I4_RGBA8888((x & 1) ? (color4B & 0x0F) : (color4B >> 4));
}

u32 GetI4_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color4B = Get4BitPaletteColor(offset, x, i);
	return I4_RGBA4444((x & 1) ? (color4B & 0x0F) : (color4B >> 4));
}

inline u8 Get8BitPaletteColor(u16 offset, u16 x, u16 i)
{
	u8* tmem8 = reinterpret_cast<u8*>(TMEM);
	return tmem8[((offset << 3) + (x ^ (i << 1))) & 0xFFF];
}

u32 GetCI8IA_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return IA88_RGBA4444(static_cast<u16>(TMEM[(0x100 + color) & 0x1FF] & 0xFFFF));
}

u32 GetCI8IA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return IA88_RGBA8888(static_cast<u16>(TMEM[(0x100 + color) & 0x1FF] & 0xFFFF));
}

u32 GetCI8RGBA_RGBA5551(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return RGBA5551_RGBA5551(static_cast<u16>(TMEM[(0x100 + color) & 0x1FF] & 0xFFFF));
}

u32 GetCI8RGBA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return RGBA5551_RGBA8888(static_cast<u16>(TMEM[(0x100 + color) & 0x1FF] & 0xFFFF));
}

u32 GetIA44_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return IA44_RGBA8888(color);
}

u32 GetIA44_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return IA44_RGBA4444(color);
}

u32 GetI8_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return I8_RGBA8888(color);
}
u32 GetI8_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u8 color = Get8BitPaletteColor(offset, x, i);
	return I8_RGBA4444(color);
}

inline u16 Get16BitColor(u16 offset, u16 x, u16 i)
{
	u16* tmem16 = reinterpret_cast<u16*>(TMEM);
	return tmem16[((offset << 2) + (x ^ i)) & 0x7FF];
}

u32 GetI16_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	u32 r = tex >> 8;
	u32 g = tex & 0xFF;
	u32 b = r;
	u32 a = g;
	return (a << 24) | (b << 16) | (g << 8) | r;
}

u32 GetI16_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	u16 r = tex >> 12;
	u16 g = tex & 0x0F;
	u16 b = r;
	u16 a = g;
	return (a << 12) | (b << 8) | (g << 4) | r;
}

u32 GetCI16IA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	const u16 col = (static_cast<u16>(TMEM[0x100 + (tex & 0xFF)] & 0xFFFF));
	const u16 c = col >> 8;
	const u16 a = col & 0xFF;
	return (a << 24) | (c << 16) | (c << 8) | c;
}

u32 GetCI16IA_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	const u16 col = (static_cast<u16>(TMEM[0x100 + (tex & 0xFF)] & 0xFFFF));
	const u16 c = col >> 12;
	const u16 a = col & 0x0F;
	return (a << 12) | (c << 8) | (c << 4) | c;
}

u32 GetCI16RGBA_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i) & 0xFF;
	return RGBA5551_RGBA8888(((u16*)&TMEM[0x100])[tex << 2]);
}

u32 GetCI16RGBA_RGBA5551(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i) & 0xFF;
	return RGBA5551_RGBA5551(((u16*)&TMEM[0x100])[tex << 2]);
}

u32 GetRGBA5551_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	return RGBA5551_RGBA8888(tex);
}

u32 GetRGBA5551_RGBA5551(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	return RGBA5551_RGBA5551(tex);
}

u32 GetIA88_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	return IA88_RGBA8888(tex);
}

u32 GetIA88_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u16 tex = Get16BitColor(offset, x, i);
	return IA88_RGBA4444(tex);
}

inline u32 Get32BitColor(u16 offset, u16 x, u16 i)
{
	u32* tmem32 = reinterpret_cast<u32*>(TMEM);
	return tmem32[((offset << 1) + (x ^ i)) & 0x3FF];
}

u32 GetRGBA8888_RGBA8888(u16 offset, u16 x, u16 i, u8 palette)
{
	return Get32BitColor(offset, x, i);
}

u32 GetRGBA8888_RGBA4444(u16 offset, u16 x, u16 i, u8 palette)
{
	const u32 tex = Get32BitColor(offset, x, i);
	return RGBA8888_RGBA4444(tex);
}

inline u32 YUV_RGBA8888(u8 y, u8 u, u8 v)
{
	return (0xff << 24) | (y << 16) | (v << 8) | u;
}

void GetYUV_RGBA8888(u64 * src, u32 * dst, u16 x)
{
	const u32 t = (((u32*)src)[x]);
	u8 y1 = (u8)t & 0xFF;
	u8 v = (u8)(t >> 8) & 0xFF;
	u8 y0 = (u8)(t >> 16) & 0xFF;
	u8 u = (u8)(t >> 24) & 0xFF;
	u32 c = YUV_RGBA8888(y0, u, v);
	*(dst++) = c;
	c = YUV_RGBA8888(y1, u, v);
	*(dst++) = c;
}

u32 GetNoneBG(u64 *src, u16 x, u16 i, u8 palette)
{
	return 0x00000000;
}

u32 GetCI4_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

	return CI4_RGBA8888((x & 1) ? (palette << 4) | (color4B & 0x0F) : (palette << 4) | (color4B >> 4));
}

u32 GetCI4_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

	return CI4_RGBA4444((x & 1) ? (palette << 4) | (color4B & 0x0F) : (palette << 4) | (color4B >> 4));
}

u32 GetCI4IA_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetCI4IA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetCI4RGBA_RGBA5551_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetCI4RGBA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetIA31_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetIA31_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetI4_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetI4_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u8 color4B = ((u8*)src)[(x >> 1) ^ (i << 1)];

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

u32 GetCI8IA_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA88_RGBA4444(static_cast<u16>(TMEM[256 + ((u8*)src)[x ^ (i << 1)]] & 0xFFFF));
}

u32 GetCI8IA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA88_RGBA8888(static_cast<u16>(TMEM[256 + ((u8*)src)[x ^ (i << 1)]] & 0xFFFF));
}

u32 GetCI8RGBA_RGBA5551_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return RGBA5551_RGBA5551(static_cast<u16>(TMEM[256 + ((u8*)src)[x ^ (i << 1)]] & 0xFFFF));
}

u32 GetCI8RGBA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return RGBA5551_RGBA8888(static_cast<u16>(TMEM[256 + ((u8*)src)[x ^ (i << 1)]] & 0xFFFF));
}

u32 GetIA44_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA44_RGBA8888(((u8*)src)[x ^ (i << 1)]);
}

u32 GetIA44_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA44_RGBA4444(((u8*)src)[x ^ (i << 1)]);
}

u32 GetI8_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return I8_RGBA8888(((u8*)src)[x ^ (i << 1)]);
}

u32 GetI8_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return I8_RGBA4444(((u8*)src)[x ^ (i << 1)]);
}

u32 GetI16_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = ((u16*)src)[x^i];
	u32 r = tex >> 8;
	u32 g = tex & 0xFF;
	u32 b = r;
	u32 a = g;
	return (a << 24) | (b << 16) | (g << 8) | r;
}

u32 GetI16_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = ((u16*)src)[x^i];
	u16 r = tex >> 12;
	u16 g = tex & 0x0F;
	u16 b = r;
	u16 a = g;
	return (a << 12) | (b << 8) | (g << 4) | r;
}

u32 GetCI16IA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = ((u16*)src)[x^i];
	const u16 col = (static_cast<u16>(TMEM[256 + (tex & 0xFF)] & 0xFFFF));
	const u16 c = col >> 8;
	const u16 a = col & 0xFF;
	return (a << 24) | (c << 16) | (c << 8) | c;
}

u32 GetCI16IA_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = ((u16*)src)[x^i];
	const u16 col = (static_cast<u16>(TMEM[256 + (tex & 0xFF)] & 0xFFFF));
	const u16 c = col >> 12;
	const u16 a = col & 0x0F;
	return (a << 12) | (c << 8) | (c << 4) | c;
}

u32 GetCI16RGBA_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = (((u16*)src)[x^i]) & 0xFF;
	return RGBA5551_RGBA8888(((u16*)&TMEM[256])[tex << 2]);
}

u32 GetCI16RGBA_RGBA5551_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	const u16 tex = (((u16*)src)[x^i]) & 0xFF;
	return RGBA5551_RGBA5551(((u16*)&TMEM[256])[tex << 2]);
}

u32 GetRGBA5551_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u16 tex = ((u16*)src)[x^i];
	return RGBA5551_RGBA8888(tex);
}

u32 GetRGBA5551_RGBA5551_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	u16 tex = ((u16*)src)[x^i];
	return RGBA5551_RGBA5551(tex);
}

u32 GetIA88_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA88_RGBA8888(((u16*)src)[x^i]);
}

u32 GetIA88_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return IA88_RGBA4444(((u16*)src)[x^i]);
}

u32 GetRGBA8888_RGBA8888_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return ((u32*)src)[x^i];
}

u32 GetRGBA8888_RGBA4444_BG(u64 *src, u16 x, u16 i, u8 palette)
{
	return RGBA8888_RGBA4444(((u32*)src)[x^i]);
}

struct TextureLoadParameters
{
	GetTexelFunc				Get16;
	GetTexelFuncBG				Get16BG;
	DatatypeParam				glType16;
	InternalColorFormatParam	glInternalFormat16;
	GetTexelFunc				Get32;
	GetTexelFuncBG				Get32BG;
	DatatypeParam				glType32;
	InternalColorFormatParam	glInternalFormat32;
	InternalColorFormatParam	autoFormat;
	u32							lineShift;
	u32							maxTexels;
};

struct ImageFormat {
	ImageFormat();

	TextureLoadParameters tlp[4][4][5];

	static ImageFormat & get() {
		static ImageFormat imageFmt;
		return imageFmt;
	}
};

ImageFormat::ImageFormat()
{
	TextureLoadParameters imageFormat[4][4][5] =
	{ // G_TT_NONE
		{ //		Get16					glType16	glInternalFormat16		Get32					glType32	glInternalFormat32	autoFormat
			{ // 4-bit
				{ GetI4_RGBA4444, GetI4_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI4_RGBA8888, GetI4_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // RGBA as I
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // YUV
				{ GetCI4_RGBA4444, GetCI4_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI4_RGBA8888, GetCI4_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // CI without palette
				{ GetIA31_RGBA4444, GetIA31_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetIA31_RGBA8888, GetIA31_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // IA
				{ GetI4_RGBA4444, GetI4_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI4_RGBA8888, GetI4_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // I
			},
			{ // 8-bit
				{ GetI8_RGBA4444, GetI8_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI8_RGBA8888, GetI8_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 4096 }, // RGBA as I
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 4096 }, // YUV
				{ GetI8_RGBA4444, GetI8_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI8_RGBA8888, GetI8_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 4096 }, // CI without palette
				{ GetIA44_RGBA4444, GetIA44_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetIA44_RGBA8888, GetIA44_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 3, 4096 }, // IA
				{ GetI8_RGBA4444, GetI8_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI8_RGBA8888, GetI8_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 4096 }, // I
			},
			{ // 16-bit
				{ GetRGBA5551_RGBA5551, GetRGBA5551_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetRGBA5551_RGBA8888, GetRGBA5551_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 2, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // YUV
				{ GetIA88_RGBA4444, GetIA88_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetIA88_RGBA8888, GetIA88_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // CI as IA
				{ GetIA88_RGBA4444, GetIA88_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetIA88_RGBA8888, GetIA88_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // IA
				{ GetI16_RGBA4444, GetI16_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetI16_RGBA8888, GetI16_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 2048 }, // I
			},
			{ // 32-bit
				{ GetRGBA8888_RGBA4444, GetRGBA8888_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetRGBA8888_RGBA8888, GetRGBA8888_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 1024 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // I
			}
		},
			// DUMMY
		{ //		Get16					glType16	glInternalFormat16			Get32				glType32	glInternalFormat32	autoFormat
			{ // 4-bit
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // CI (Banjo-Kazooie uses this, doesn't make sense, but it works...)
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // YUV
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // CI
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // IA as CI
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // I as CI
			},
			{ // 8-bit
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 4096 }, // YUV
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // CI
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // IA as CI
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // I as CI
			},
			{ // 16-bit
				{ GetCI16RGBA_RGBA5551, GetCI16RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetRGBA5551_RGBA8888, GetRGBA5551_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 2, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 2048 }, // CI
				{ GetCI16RGBA_RGBA5551, GetCI16RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI16RGBA_RGBA8888, GetCI16RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 2, 2048 }, // IA as CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 2048 }, // I
			},
			{ // 32-bit
				{ GetRGBA8888_RGBA4444, GetRGBA8888_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetRGBA8888_RGBA8888, GetRGBA8888_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 1024 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // I
			}
		},
			// G_TT_RGBA16
		{ //		Get16					glType16			glInternalFormat16	Get32				glType32	glInternalFormat32	autoFormat
			{ // 4-bit
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // CI (Banjo-Kazooie uses this, doesn't make sense, but it works...)
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 4, 8192 }, // YUV
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // CI
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // IA as CI
				{ GetCI4RGBA_RGBA5551, GetCI4RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI4RGBA_RGBA8888, GetCI4RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 4, 4096 }, // I as CI
			},
			{ // 8-bit
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 4096 }, // YUV
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // CI
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // IA as CI
				{ GetCI8RGBA_RGBA5551, GetCI8RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI8RGBA_RGBA8888, GetCI8RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 3, 2048 }, // I as CI
			},
			{ // 16-bit
				{ GetCI16RGBA_RGBA5551, GetCI16RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetRGBA5551_RGBA8888, GetRGBA5551_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 2, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 2048 }, // CI
				{ GetCI16RGBA_RGBA5551, GetCI16RGBA_RGBA5551_BG, datatype::UNSIGNED_SHORT_5_5_5_1, internalcolorFormat::RGB5_A1, GetCI16RGBA_RGBA8888, GetCI16RGBA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGB5_A1, 2, 2048 }, // IA as CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 2048 }, // I
			},
			{ // 32-bit
				{ GetRGBA8888_RGBA4444, GetRGBA8888_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetRGBA8888_RGBA8888, GetRGBA8888_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 1024 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA4, 0, 1024 }, // I
			}
		},
			// G_TT_IA16
		{ //		Get16					glType16			glInternalFormat16	Get32				glType32	glInternalFormat32	autoFormat
			{ // 4-bit
				{ GetCI4IA_RGBA4444, GetCI4IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI4IA_RGBA8888, GetCI4IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 4, 4096 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 4, 8192 }, // YUV
				{ GetCI4IA_RGBA4444, GetCI4IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI4IA_RGBA8888, GetCI4IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 4, 4096 }, // CI
				{ GetCI4IA_RGBA4444, GetCI4IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI4IA_RGBA8888, GetCI4IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 4, 4096 }, // IA
				{ GetCI4IA_RGBA4444, GetCI4IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI4IA_RGBA8888, GetCI4IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 4, 4096 }, // I
			},
			{ // 8-bit
				{ GetCI8IA_RGBA4444, GetCI8IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI8IA_RGBA8888, GetCI8IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 4096 }, // YUV
				{ GetCI8IA_RGBA4444, GetCI8IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI8IA_RGBA8888, GetCI8IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 2048 }, // CI
				{ GetCI8IA_RGBA4444, GetCI8IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI8IA_RGBA8888, GetCI8IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 2048 }, // IA
				{ GetCI8IA_RGBA4444, GetCI8IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI8IA_RGBA8888, GetCI8IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 3, 2048 }, // I
			},
			{ // 16-bit
				{ GetCI16IA_RGBA4444, GetCI16IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI16IA_RGBA8888, GetCI16IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 2048 }, // CI
				{ GetCI16IA_RGBA4444, GetCI16IA_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetCI16IA_RGBA8888, GetCI16IA_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 2048 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 2048 }, // I
			},
			{ // 32-bit
				{ GetRGBA8888_RGBA4444, GetRGBA8888_RGBA4444_BG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetRGBA8888_RGBA8888, GetRGBA8888_RGBA8888_BG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 2, 1024 }, // RGBA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 1024 }, // YUV
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 1024 }, // CI
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 1024 }, // IA
				{ GetNone, GetNoneBG, datatype::UNSIGNED_SHORT_4_4_4_4, internalcolorFormat::RGBA4, GetNone, GetNoneBG, datatype::UNSIGNED_BYTE, internalcolorFormat::RGBA8, internalcolorFormat::RGBA8, 0, 1024 }, // I
			}
		}
	};

	memcpy(tlp, imageFormat, sizeof(tlp));
}

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

u32 sizeBytes[4] = {0, 1, 2, 4};

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

inline u32 ReverseDXT(u32 val, u32 lrs, u32 width, u32 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) == (u32)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->maskS = 0;
	_pDummy->maskT = 0;
	_pDummy->scaleS = 0.5f;
	_pDummy->scaleT = 0.5f;
	_pDummy->hdRatioS = 1.0f;
	_pDummy->hdRatioT = 1.0f;
	_pDummy->shiftScaleS = 1.0f;
	_pDummy->shiftScaleT = 1.0f;
	_pDummy->textureBytes = 2 * 2 * 4;
	_pDummy->tMem = 0;
}

void TextureCache::init()
{
	m_curUnpackAlignment = 0;

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

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

	Context::InitTextureParams params;
	params.handle = m_pDummy->name;
	params.mipMapLevel = 0;
	params.msaaLevel = 0;
	params.width = m_pDummy->width;
	params.height = m_pDummy->height;
	params.format = colorFormat::RGBA;
	params.internalFormat = gfxContext.convertInternalTextureFormat(u32(internalcolorFormat::RGBA8));
	params.dataType = datatype::UNSIGNED_BYTE;
	params.data = dummyTexture;
	gfxContext.init2DTexture(params);

	activateDummy( 0 );
	activateDummy(1);
	current[0] = current[1] = nullptr;


	m_pMSDummy = nullptr;
	if (config.video.multisampling != 0 && Context::Multisampling) {
		m_pMSDummy = addFrameBufferTexture(textureTarget::TEXTURE_2D_MULTISAMPLE); // we don't want to remove dummy texture
		_initDummyTexture(m_pMSDummy);

		Context::InitTextureParams msParams;
		msParams.handle = m_pMSDummy->name;
		msParams.mipMapLevel = 0;
		msParams.msaaLevel = config.video.multisampling;
		msParams.width = m_pMSDummy->width;
		msParams.height = m_pMSDummy->height;
		msParams.format = colorFormat::RGBA;
		msParams.internalFormat = gfxContext.convertInternalTextureFormat(u32(internalcolorFormat::RGBA8));
		msParams.dataType = datatype::UNSIGNED_BYTE;
		gfxContext.init2DTexture(msParams);

		activateMSDummy(0);
		activateMSDummy(1);
	}

	assert(!gfxContext.isError());
}

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

	for (Textures::const_iterator cur = m_textures.cbegin(); cur != m_textures.cend(); ++cur)
		gfxContext.deleteTexture(cur->name);
	m_textures.clear();
	m_lruTextureLocations.clear();

	for (FBTextures::const_iterator cur = m_fbTextures.cbegin(); cur != m_fbTextures.cend(); ++cur)
		gfxContext.deleteTexture(cur->second.name);
	m_fbTextures.clear();

	m_hdTexCacheSize = 0;
}

void TextureCache::_checkHdTexLimit()
{
	const u64 maxCacheSize = config.textureFilter.txHiresVramLimit * 1024u * 1024u;

	// we don't need to do anything,
	// when the limit has been disabled
	if (maxCacheSize == 0u)
		return;

	// keep removing hd textures until we're below the max size
	for (auto iter = m_textures.rbegin(); iter != m_textures.rend() && m_hdTexCacheSize >= maxCacheSize;)
	{
		if (!iter->bHDTexture) {
			++iter;
		} else {
			assert(m_hdTexCacheSize >= iter->textureBytes);
			m_hdTexCacheSize -= iter->textureBytes;
			gfxContext.deleteTexture(iter->name);
			m_lruTextureLocations.erase(iter->crc);
			iter = decltype(iter)(m_textures.erase(std::next(iter).base()));
		}
	}
}

void TextureCache::_checkCacheSize()
{
	size_t m_maxCacheSize = MaxTxCacheSize;
	if (m_textures.size() >= m_maxCacheSize) {
		CachedTexture& clsTex = m_textures.back();
		if (clsTex.bHDTexture)
			m_hdTexCacheSize -= clsTex.textureBytes;
		gfxContext.deleteTexture(clsTex.name);
		m_lruTextureLocations.erase(clsTex.crc);
		m_textures.pop_back();
	}
}

CachedTexture * TextureCache::_addTexture(u64 _crc64)
{
	if (m_curUnpackAlignment == 0)
		m_curUnpackAlignment = gfxContext.getTextureUnpackAlignment();
	_checkCacheSize();
	m_textures.emplace_front(gfxContext.createTexture(textureTarget::TEXTURE_2D));
	Textures::iterator new_iter = m_textures.begin();
	new_iter->crc = _crc64;
	m_lruTextureLocations.insert(std::pair<u64, Textures::iterator>(_crc64, new_iter));
	return &(*new_iter);
}

void TextureCache::removeFrameBufferTexture(CachedTexture * _pTexture)
{
	if (_pTexture == nullptr)
		return;
	FBTextures::const_iterator iter = m_fbTextures.find(u32(_pTexture->name));
	assert(iter != m_fbTextures.cend());

	if (iter != m_fbTextures.cend()) {
		gfxContext.deleteTexture(ObjectHandle(iter->second.name));
		m_fbTextures.erase(iter);
	}
}

CachedTexture * TextureCache::addFrameBufferTexture(graphics::Parameter _target)
{
	ObjectHandle texName(gfxContext.createTexture(_target));
	m_fbTextures.emplace(u32(texName), texName);
	return &m_fbTextures.at(u32(texName));
}

struct TileSizes
{
	u32 clampWidth = 0U;
	u32 width = 0U;
	u32 clampHeight = 0U;
	u32 height = 0U;
	u32 bytes = 0U;
};

static
void _calcTileSizes(u32 _t, TileSizes & _sizes, gDPTile * _pLoadTile)
{
	gDPTile * pTile = _t < 2 ? gSP.textureTile[_t] : &gDP.tiles[_t];
	pTile->masks = pTile->originalMaskS;
	pTile->maskt = pTile->originalMaskT;

	u32 tileWidth = ((pTile->lrs - pTile->uls) & 0x03FF) + 1;
	u32 tileHeight = ((pTile->lrt - pTile->ult) & 0x03FF) + 1;

	const u32 tMemMask = gDP.otherMode.textureLUT == G_TT_NONE ? 0x1FF : 0xFF;
	gDPLoadTileInfo &info = gDP.loadInfo[pTile->tmem & tMemMask];
	if (pTile->tmem == gDP.loadTile->tmem) {
		if (gDP.loadTile->loadWidth != 0 && gDP.loadTile->masks == 0)
			info.width = gDP.loadTile->loadWidth;
		if (gDP.loadTile->loadHeight != 0 && gDP.loadTile->maskt == 0) {
			info.height = gDP.loadTile->loadHeight;
			info.bytes = info.height * (gDP.loadTile->line << 3);
			if (gDP.loadTile->size == G_IM_SIZ_32b)
				// 32 bit texture loaded into lower and upper half of TMEM, thus actual bytes doubled.
				info.bytes *= 2;
		}
	}

	gDP.loadTile->loadWidth = gDP.loadTile->loadHeight = 0;
	_sizes.bytes = info.bytes;

	if (tileWidth == 1 && tileHeight == 1 &&
		gDP.otherMode.cycleType == G_CYC_COPY &&
		_pLoadTile != nullptr) {
		const u32 ulx = _SHIFTR(RDP.w1, 14, 10);
		const u32 uly = _SHIFTR(RDP.w1, 2, 10);
		const u32 lrx = _SHIFTR(RDP.w0, 14, 10);
		const u32 lry = _SHIFTR(RDP.w0, 2, 10);
		tileWidth = lrx - ulx + 1;
		tileHeight = lry - uly + 1;
	}

	u32 width = 0, height = 0;
	if (info.loadType == LOADTYPE_TILE) {
		width = min(info.width, info.texWidth);
		if (width == 0)
			width = tileWidth;
		if (info.size > pTile->size)
			width <<= info.size - pTile->size;

		height = info.height;
		if (height == 0)
			height = tileHeight;
		if ((config.generalEmulation.hacks & hack_MK64) != 0 && (height % 2) != 0)
			height--;
	} else {
		const TextureLoadParameters & loadParams =
			ImageFormat::get().tlp[gDP.otherMode.textureLUT][pTile->size][pTile->format];

		int tile_width = pTile->lrs - pTile->uls + 1;
		int tile_height = pTile->lrt - pTile->ult + 1;

		int mask_width = (pTile->masks == 0) ? (tile_width) : (1 << pTile->masks);
		int mask_height = (pTile->maskt == 0) ? (tile_height) : (1 << pTile->maskt);

		if (pTile->clamps)
			width = min(mask_width, tile_width);
		else if ((u32)(mask_width * mask_height) <= loadParams.maxTexels)
			width = mask_width;
		else
			width = tileWidth;

		if (pTile->clampt)
			height = min(mask_height, tile_height);
		else if ((u32)(mask_width * mask_height) <= loadParams.maxTexels)
			height = mask_height;
		else
			height = tileHeight;
	}

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

	_sizes.width = (info.loadType == LOADTYPE_TILE &&
					pTile->clamps != 0 &&
					pTile->masks == 0) ?
					_sizes.clampWidth :
					width;
	_sizes.height = (info.loadType == LOADTYPE_TILE &&
					pTile->clampt != 0 &&
					pTile->maskt == 0) ?
					_sizes.clampHeight :
					height;
}

void TextureCache::_updateCachedTexture(const GHQTexInfo & _info, CachedTexture *_pTexture, u16 widthOrg, u16 heightOrg)
{
	_pTexture->textureBytes = _info.width * _info.height;

	Parameter format(_info.format);
	if (format == internalcolorFormat::RGB8 ||
		format == internalcolorFormat::RGBA4 ||
		format == internalcolorFormat::RGB5_A1) {
		_pTexture->textureBytes <<= 1;
	} else {
		_pTexture->textureBytes <<= 2;
	}

	_pTexture->scaleS = 1.0f / (_pTexture->maskS ? f32(pow2(widthOrg)) : f32(widthOrg));
	_pTexture->scaleT = 1.0f / (_pTexture->maskT ? f32(pow2(heightOrg)) : f32(heightOrg));

	_pTexture->hdRatioS = (f32)(_info.width) / (f32)(_pTexture->width);
	_pTexture->hdRatioT = (f32)(_info.height) / (f32)(_pTexture->height);

	_pTexture->bHDTexture = true;

	m_hdTexCacheSize += _pTexture->textureBytes;
	_checkHdTexLimit();
}

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

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

	u8 * paladdr = nullptr;
	u16 * palette = nullptr;
	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 = (u8*)(gDP.TexFilterPalette);
		else if (config.textureFilter.txHresAltCRC)
			paladdr = (u8*)(gDP.TexFilterPalette + (gSP.bgImage.palette << 5));
		else
			paladdr = (u8*)(gDP.TexFilterPalette + (gSP.bgImage.palette << 4));
		// TODO: fix palette load
		//			palette = (rdp.pal_8 + (gSP.textureTile[_t]->palette << 4));
	}

	_ricecrc = txfilter_checksum(addr, tile_width,
						tile_height, gSP.bgImage.size, bpl, paladdr);
	GHQTexInfo ghqTexInfo;
	// TODO: fix problem with zero texture dimensions on GLideNHQ side.
	if (txfilter_hirestex(_pTexture->crc, _ricecrc, palette, N64FormatSize(_pTexture->format, _pTexture->size), &ghqTexInfo) &&
			ghqTexInfo.width != 0 && ghqTexInfo.height != 0) {
		ghqTexInfo.format = gfxContext.convertInternalTextureFormat(ghqTexInfo.format);
		Context::InitTextureParams params;
		params.handle = _pTexture->name;
		params.mipMapLevel = 0;
		params.msaaLevel = 0;
		params.width = ghqTexInfo.width;
		params.height = ghqTexInfo.height;
		params.format = ColorFormatParam(ghqTexInfo.texture_format);
		params.internalFormat = InternalColorFormatParam(ghqTexInfo.format);
		params.dataType = DatatypeParam(ghqTexInfo.pixel_type);
		params.data = ghqTexInfo.data;
		gfxContext.init2DTexture(params);

		assert(!gfxContext.isError());
		_updateCachedTexture(ghqTexInfo, _pTexture, tile_width, tile_height);
		return true;
	}
	return false;
}

void TextureCache::_loadBackground(CachedTexture *pTexture)
{
	u64 ricecrc = 0;
	if (_loadHiresBackground(pTexture, ricecrc))
		return;

	u32 *pDest = nullptr;
	u16 *pDest16 = nullptr;

	u8 *pSwapped, *pSrc;
	u32 numBytes, bpl;
	u32 x, y, j, tx, ty;
	u16 clampSClamp;
	u16 clampTClamp;
	GetTexelFuncBG GetTexel;
	InternalColorFormatParam glInternalFormat;
	DatatypeParam glType;

	const TextureLoadParameters & loadParams =
			ImageFormat::get().tlp[pTexture->format == 2 ? G_TT_RGBA16 : G_TT_NONE][pTexture->size][pTexture->format];
	if (loadParams.autoFormat == internalcolorFormat::RGBA8) {
		pTexture->textureBytes = (pTexture->width * pTexture->height) << 2;
		GetTexel = loadParams.Get32BG;
		glInternalFormat = loadParams.glInternalFormat32;
		glType = loadParams.glType32;
	} else {
		pTexture->textureBytes = (pTexture->width * pTexture->height) << 1;
		GetTexel = loadParams.Get16BG;
		glInternalFormat = loadParams.glInternalFormat16;
		glType = loadParams.glType16;
	}

	bpl = gSP.bgImage.width << gSP.bgImage.size >> 1;
	numBytes = bpl * gSP.bgImage.height;
	pSwapped = (u8*)malloc(numBytes);
	if (pSwapped == nullptr)
		return;
	UnswapCopyWrap(RDRAM, gSP.bgImage.address, pSwapped, 0, RDRAMSize, numBytes);
	pDest = (u32*)malloc(pTexture->textureBytes);
	if (pDest == nullptr) {
		free(pSwapped);
		return;
	}
	pDest16 = reinterpret_cast<u16*>(pDest);

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

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

		pSrc = &pSwapped[bpl * ty];

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

			if (glInternalFormat == internalcolorFormat::RGBA8)
				pDest[j++] = GetTexel((u64*)pSrc, tx, 0, pTexture->palette);
			else
				pDest16[j++] = static_cast<u16>(GetTexel((u64*)pSrc, tx, 0, pTexture->palette));
		}
	}

	if ((config.generalEmulation.hacks&hack_LoadDepthTextures) != 0 && gDP.colorImage.address == gDP.depthImageAddress) {
		_loadDepthTexture(pTexture, (u16*)pDest);
		free(pDest);
		free(pSwapped);
		return;
	}

	if ((m_toggleDumpTex &&
		config.textureFilter.txHiresEnable != 0 &&
		config.hotkeys.enabledKeys[Config::HotKey::hkTexDump] != 0) ||
		config.textureFilter.txDump) {
		txfilter_dmptx((u8*)pDest, pTexture->width, pTexture->height,
			pTexture->width, (u16)u32(glInternalFormat),
			N64FormatSize(pTexture->format, pTexture->size),
			ricecrc);
	}

	bool bLoaded = false;
	if ((config.textureFilter.txEnhancementMode | config.textureFilter.txFilterMode) != 0 &&
			config.textureFilter.txFilterIgnoreBG == 0 &&
			TFH.isInited()) {
		GHQTexInfo ghqTexInfo;
		if (txfilter_filter((u8*)pDest, pTexture->width, pTexture->height,
				(u16)u32(glInternalFormat), pTexture->crc, N64FormatSize(pTexture->format, pTexture->size), &ghqTexInfo) != 0 &&
				ghqTexInfo.data != nullptr) {

			if (ghqTexInfo.width % 2 != 0 &&
				ghqTexInfo.format != u32(internalcolorFormat::RGBA8) &&
				m_curUnpackAlignment > 1)
				gfxContext.setTextureUnpackAlignment(2);

			ghqTexInfo.format = gfxContext.convertInternalTextureFormat(ghqTexInfo.format);
			Context::InitTextureParams params;
			params.handle = pTexture->name;
			params.mipMapLevel = 0;
			params.msaaLevel = 0;
			params.width = ghqTexInfo.width;
			params.height = ghqTexInfo.height;
			params.format = ColorFormatParam(ghqTexInfo.texture_format);
			params.internalFormat = InternalColorFormatParam(ghqTexInfo.format);
			params.dataType = DatatypeParam(ghqTexInfo.pixel_type);
			params.data = ghqTexInfo.data;
			gfxContext.init2DTexture(params);
			_updateCachedTexture(ghqTexInfo, pTexture, pTexture->width, pTexture->height);
			bLoaded = true;
		}
	}
	if (!bLoaded) {
		if (pTexture->width % 2 != 0 && glInternalFormat != internalcolorFormat::RGBA8)
			gfxContext.setTextureUnpackAlignment(2);
		Context::InitTextureParams params;
		params.handle = pTexture->name;
		params.mipMapLevel = 0;
		params.msaaLevel = 0;
		params.width = pTexture->width;
		params.height = pTexture->height;
		params.format = colorFormat::RGBA;
		params.internalFormat = gfxContext.convertInternalTextureFormat(u32(glInternalFormat));
		params.dataType = glType;
		params.data = pDest;
		gfxContext.init2DTexture(params);
	}
	if (m_curUnpackAlignment > 1)
		gfxContext.setTextureUnpackAlignment(m_curUnpackAlignment);
	free(pSwapped);
	free(pDest);
}

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

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

	// Temporal workaround for crash problem with mip-mapped textures. See #1711 for details.
	// TODO: make proper fix.
	if (info.texAddress == 0)
		return false;

	int bpl;
	int width, height;
	u8 * addr = (u8*)(RDRAM + info.texAddress);
	if (info.loadType == LOADTYPE_TILE) {
		bpl = info.texWidth << info.size >> 1;
		addr += (info.ult * bpl) + (((info.uls << info.size) + 1) >> 1);

		width = min(info.width, info.texWidth);
		if (info.size > _pTexture->size)
			width <<= info.size - _pTexture->size;

		height = info.height;
		if ((config.generalEmulation.hacks & hack_MK64) != 0 && (height % 2) != 0)
			height--;
	} else {
		const gDPTile * pTile = gSP.textureTile[_tile];
		int tile_width = ((pTile->lrs - pTile->uls) & 0x03FF) + 1;
		int tile_height = ((pTile->lrt - pTile->ult) & 0x03FF) + 1;

		int mask_width = (pTile->masks == 0) ? (tile_width) : (1 << pTile->masks);
		int mask_height = (pTile->maskt == 0) ? (tile_height) : (1 << pTile->maskt);

		if ((pTile->clamps && tile_width <= 256))
			width = min(mask_width, tile_width);
		else
			width = mask_width;

		if ((pTile->clampt && tile_height <= 256) || (mask_height > 256))
			height = min(mask_height, tile_height);
		else
			height = mask_height;

		if (pTile->size == G_IM_SIZ_32b)
			bpl = pTile->line << 4;
		else if (info.dxt == 0)
			bpl = pTile->line << 3;
		else {
			u32 dxt = info.dxt;
			if (dxt > 1)
				dxt = ReverseDXT(dxt, info.width, _pTexture->width, _pTexture->size);
			bpl = dxt << 3;
		}
	}

	u8 * paladdr = nullptr;
	u16 * palette = nullptr;
	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 = (u8*)(gDP.TexFilterPalette);
		else if (config.textureFilter.txHresAltCRC)
			paladdr = (u8*)(gDP.TexFilterPalette + (_pTexture->palette << 5));
		else
			paladdr = (u8*)(gDP.TexFilterPalette + (_pTexture->palette << 4));
		// TODO: fix palette load
		//			palette = (rdp.pal_8 + (gSP.textureTile[_t]->palette << 4));
	}

	_ricecrc = txfilter_checksum(addr, width, height, _pTexture->size, bpl, paladdr);
	if (config.textureFilter.txStrongCRC)
		_strongcrc = txfilter_checksum_strong(addr, width, height, _pTexture->size, bpl, paladdr);
	GHQTexInfo ghqTexInfo;
	// TODO: fix problem with zero texture dimensions on GLideNHQ side.
	auto hirestexFound = txfilter_hirestex(_pTexture->crc, _ricecrc, palette, N64FormatSize(_pTexture->format, _pTexture->size), &ghqTexInfo);
	if (!hirestexFound) {
		// Texture with RiceCRC was not found. Try alternative CRC.
		if (_strongcrc == 0U)
			_strongcrc = txfilter_checksum_strong(addr, width, height, _pTexture->size, bpl, paladdr);
		hirestexFound = txfilter_hirestex(_pTexture->crc, _strongcrc, palette, N64FormatSize(_pTexture->format, _pTexture->size), &ghqTexInfo);
	}
	if (hirestexFound && ghqTexInfo.width != 0 && ghqTexInfo.height != 0) {
		ghqTexInfo.format = gfxContext.convertInternalTextureFormat(ghqTexInfo.format);
		Context::InitTextureParams params;
		params.handle = _pTexture->name;
		params.mipMapLevel = 0;
		params.msaaLevel = 0;
		params.width = ghqTexInfo.width;
		params.height = ghqTexInfo.height;
		params.internalFormat = InternalColorFormatParam(ghqTexInfo.format);
		params.format = ColorFormatParam(ghqTexInfo.texture_format);
		params.dataType = DatatypeParam(ghqTexInfo.pixel_type);
		params.data = ghqTexInfo.data;
		params.textureUnitIndex = textureIndices::Tex[_tile];
		gfxContext.init2DTexture(params);
		assert(!gfxContext.isError());
		_updateCachedTexture(ghqTexInfo, _pTexture, width, height);
		return true;
	}

	return false;
}

void TextureCache::_loadDepthTexture(CachedTexture * _pTexture, u16* _pDest)
{
	if (!config.generalEmulation.enableFragmentDepthWrite)
		return;

	u32 size = _pTexture->width * _pTexture->height;
	std::vector<f32> pDestFloat(size);
	for (u32 i = 0; i < size; ++i)
		pDestFloat[i] = _pDest[i] / 65535.0f;

	Context::InitTextureParams params;
	params.handle = _pTexture->name;
	params.mipMapLevel = 0;
	params.msaaLevel = 0;
	params.width = _pTexture->width;
	params.height = _pTexture->height;
	params.internalFormat = internalcolorFormat::R16F;
	params.format = colorFormat::RED;
	params.dataType = datatype::FLOAT;
	params.data = pDestFloat.data();
	gfxContext.init2DTexture(params);
}

/*
 * Worker function for _load
*/
void TextureCache::_getTextureDestData(CachedTexture& tmptex,
						u32* pDest,
						Parameter glInternalFormat,
						GetTexelFunc GetTexel,
						u16* pLine)
{
	u16 maskSMask, clampSClamp;
	u16 maskTMask, clampTClamp;
	u16 x, y, tx, ty;
	u32 i, j;

	if (tmptex.maskS > 0) {
		clampSClamp = tmptex.clampS ? tmptex.clampWidth - 1 : (tmptex.mirrorS ? (tmptex.width << 1) - 1 : tmptex.width - 1);
		maskSMask = (1 << tmptex.maskS) - 1;
	} else {
		clampSClamp = tmptex.clampS ? tmptex.clampWidth - 1 : tmptex.width - 1;
		maskSMask = 0xFFFF;
	}

	if (tmptex.maskT > 0) {
		clampTClamp = tmptex.clampT ? tmptex.clampHeight - 1 : (tmptex.mirrorT ? (tmptex.height << 1) - 1 : tmptex.height - 1);
		maskTMask = (1 << tmptex.maskT) - 1;
	} else {
		clampTClamp = tmptex.clampT ? tmptex.clampHeight - 1 : tmptex.height - 1;
		maskTMask = 0xFFFF;
	}

	if (tmptex.size == G_IM_SIZ_32b) {
		const u16 * tmem16 = (u16*)TMEM;
		const u32 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);

		u16 gr, ab;

		j = 0;
		for (y = 0; y < tmptex.height; ++y) {
			ty = min(y, clampTClamp) & maskTMask;

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

			for (x = 0; x < tmptex.width; ++x) {
				tx = min(x, clampSClamp) & maskSMask;

				u32 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.height; ++y) {
			u64* pSrc = &TMEM[tmptex.tMem] + *pLine * y;
			for (x = 0; x < tmptex.width / 2; x++) {
				GetYUV_RGBA8888(pSrc, pDest + j, x);
				j += 2;
			}
		}
	} else {
		j = 0;
		const u32 tMemMask = gDP.otherMode.textureLUT == G_TT_NONE ? 0x1FF : 0xFF;
		for (y = 0; y < tmptex.height; ++y) {
			ty = min(y, clampTClamp) & maskTMask;

			u16 tmemOffset = (tmptex.tMem + *pLine * ty) & tMemMask;

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

				if (glInternalFormat == internalcolorFormat::RGBA8)
					pDest[j++] = GetTexel(tmemOffset, tx, i, tmptex.palette);
				else
					((u16*)pDest)[j++] = GetTexel(tmemOffset, tx, i, tmptex.palette);
			}
		}
	}
}

template<typename T>
void doubleTexture(T* pTex, u32 width, u32 height)
{
	std::vector<T> vData(width * height);
	memcpy(vData.data(), pTex, width * height * sizeof(T));
	u32 srcIdx = 0;
	u32 dstIdx = 0;
	for (u32 y = 0; y < height; ++y) {
		const u32 srcIdxCur = srcIdx;
		for (u32 x = 0; x < width; ++x) {
			pTex[dstIdx++] = vData[srcIdx];
			pTex[dstIdx++] = vData[srcIdx++];
		}
		srcIdx = srcIdxCur;
		for (u32 x = 0; x < width; ++x) {
			pTex[dstIdx++] = vData[srcIdx];
			pTex[dstIdx++] = vData[srcIdx++];
		}
	}
}

void TextureCache::_loadFast(u32 _tile, CachedTexture *_pTexture)
{
	u64 ricecrc = 0;
	u64 strongcrc = 0;
	if (_loadHiresTexture(_tile, _pTexture, ricecrc, strongcrc))
		return;

	s32 mipLevel = 0;
	bool force32bitFormat = false;
	_pTexture->max_level = 0;

	if (config.generalEmulation.enableLOD != 0 && gSP.texture.level > 1) {
		if (_tile == 0) {
			_pTexture->max_level = 0;
		} else {
			_pTexture->max_level = static_cast<u8>(gSP.texture.level - 1);
			const u16 dim = std::max(_pTexture->width, _pTexture->height);
			while (dim <  static_cast<u16>(1 << _pTexture->max_level))
				--_pTexture->max_level;

			auto texFormat = gDP.tiles[gSP.texture.tile + 1].format;
			auto texSize = gDP.tiles[gSP.texture.tile + 1].size;
			u32 tileMipLevel = gSP.texture.tile + 2;
			while (!force32bitFormat && (tileMipLevel < gSP.texture.tile + gSP.texture.level)) {
				gDPTile const& mipTile = gDP.tiles[tileMipLevel++];
				force32bitFormat = texFormat != mipTile.format || texSize != mipTile.size;
			}
		}
	}

	u32 sizeShift = 1;
	{
		const TextureLoadParameters & loadParams =
				ImageFormat::get().tlp[gDP.otherMode.textureLUT][_pTexture->size][_pTexture->format];
		if (force32bitFormat || loadParams.autoFormat == internalcolorFormat::RGBA8)
			sizeShift = 2;
	}
	_pTexture->textureBytes = (_pTexture->width * _pTexture->height) << sizeShift;

	unsigned int totalTexSize = std::max(static_cast<u32>(_pTexture->textureBytes/sizeof(u32) + 8), MIPMAP_TILE_WIDTH)
								* (_pTexture->max_level + 1);

	if (m_tempTextureHolder.size() < totalTexSize) {
		m_tempTextureHolder.resize(totalTexSize);
	}

	GetTexelFunc GetTexel;
	InternalColorFormatParam glInternalFormat;
	DatatypeParam glType;

	auto getLoadParams = [&](u16 _format, u16 _size)
	{
		const TextureLoadParameters & loadParams =
				ImageFormat::get().tlp[gDP.otherMode.textureLUT][_size][_format];
		if (force32bitFormat || loadParams.autoFormat == internalcolorFormat::RGBA8) {
			GetTexel = loadParams.Get32;
			glInternalFormat = loadParams.glInternalFormat32;
			glType = loadParams.glType32;
		}
		else {
			GetTexel = loadParams.Get16;
			glInternalFormat = loadParams.glInternalFormat16;
			glType = loadParams.glType16;
		}
	};

	CachedTexture tmptex = *_pTexture;
	u16 line = tmptex.line;

	while (true) {
		getLoadParams(tmptex.format, tmptex.size);
		{
			const u32 tileMipLevel = gSP.texture.tile + mipLevel + 1;
			gDPTile & mipTile = gDP.tiles[tileMipLevel];
			if (tmptex.max_level > 1 &&
				tmptex.width == (mipTile.lrs - mipTile.uls + 1) * 2 &&
				tmptex.height == (mipTile.lrt - mipTile.ult + 1) * 2)
			{
				// Special case for Southern Swamp grass texture, Zelda MM. See #2315
				const u16 texWidth = tmptex.width;
				const u16 texHeight = tmptex.height;
				tmptex.width = mipTile.lrs - mipTile.uls + 1;
				tmptex.height = mipTile.lrt - mipTile.ult + 1;
				_getTextureDestData(tmptex, m_tempTextureHolder.data(), glInternalFormat, GetTexel, &line);
				if (sizeShift == 2)
					doubleTexture<u32>(m_tempTextureHolder.data(), tmptex.width, tmptex.height);
				else
					doubleTexture<u16>((u16*)m_tempTextureHolder.data(), tmptex.width, tmptex.height);
				tmptex.width = texWidth;
				tmptex.height = texHeight;
			} else {
				_getTextureDestData(tmptex, m_tempTextureHolder.data(), glInternalFormat, GetTexel, &line);
			}
		}

		if ((config.generalEmulation.hacks&hack_LoadDepthTextures) != 0 && gDP.colorImage.address == gDP.depthImageAddress) {
			_loadDepthTexture(_pTexture, (u16*)m_tempTextureHolder.data());
			return;
		}

		if ((m_toggleDumpTex &&
			config.textureFilter.txHiresEnable != 0 &&
			config.hotkeys.enabledKeys[Config::HotKey::hkTexDump] != 0) ||
			config.textureFilter.txDump) {
				config.textureFilter.txStrongCRC ?
				txfilter_dmptx_strong((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
					tmptex.width, (u16)u32(glInternalFormat),
					N64FormatSize(_pTexture->format, _pTexture->size),
					strongcrc) :
				txfilter_dmptx((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
					tmptex.width, (u16)u32(glInternalFormat),
					N64FormatSize(_pTexture->format, _pTexture->size),
					ricecrc);
		}

		bool bLoaded = false;
		bool needEnhance = (config.textureFilter.txEnhancementMode | config.textureFilter.txFilterMode) != 0 &&
						   _pTexture->max_level == 0 &&
						   TFH.isInited();
		if (needEnhance) {
			if (config.textureFilter.txFilterIgnoreBG != 0) {
				switch (GBI.getMicrocodeType()) {
					case S2DEX_1_07:
					case S2DEX_1_03:
					case S2DEX_1_05:
						needEnhance = RSP.cmd != 0x01 && RSP.cmd != 0x02;
						break;
					case S2DEX2:
						needEnhance = RSP.cmd != 0x09 && RSP.cmd != 0x0A;
						break;
				}
			}
		}

		if (needEnhance) {
			GHQTexInfo ghqTexInfo;
			if (txfilter_filter((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
								(u16)u32(glInternalFormat), (uint64)_pTexture->crc,
								N64FormatSize(_pTexture->format, _pTexture->size),
								&ghqTexInfo) != 0 && ghqTexInfo.data != nullptr) {
				if (ghqTexInfo.width % 2 != 0 &&
					ghqTexInfo.format != u32(internalcolorFormat::RGBA8) &&
					m_curUnpackAlignment > 1)
					gfxContext.setTextureUnpackAlignment(2);
				ghqTexInfo.format = gfxContext.convertInternalTextureFormat(ghqTexInfo.format);
				Context::InitTextureParams params;
				params.handle = _pTexture->name;
				params.textureUnitIndex = textureIndices::Tex[_tile];
				params.mipMapLevel = 0;
				params.msaaLevel = 0;
				params.width = ghqTexInfo.width;
				params.height = ghqTexInfo.height;
				params.internalFormat = InternalColorFormatParam(ghqTexInfo.format);
				params.format = ColorFormatParam(ghqTexInfo.texture_format);
				params.dataType = DatatypeParam(ghqTexInfo.pixel_type);
				params.data = ghqTexInfo.data;
				gfxContext.init2DTexture(params);
				_updateCachedTexture(ghqTexInfo, _pTexture, tmptex.width, tmptex.height);
				bLoaded = true;
			}
		}
		if (!bLoaded) {
			if (tmptex.width % 2 != 0 &&
				glInternalFormat != internalcolorFormat::RGBA8 &&
				m_curUnpackAlignment > 1)
				gfxContext.setTextureUnpackAlignment(2);
			Context::InitTextureParams params;
			params.handle = _pTexture->name;
			params.textureUnitIndex = textureIndices::Tex[_tile];
			params.mipMapLevel = mipLevel;
			params.mipMapLevels = _pTexture->max_level + 1;
			params.msaaLevel = 0;
			params.width = tmptex.width;
			params.height = tmptex.height;
			params.internalFormat = gfxContext.convertInternalTextureFormat(u32(glInternalFormat));
			params.format = colorFormat::RGBA;
			params.dataType = glType;
			params.data = m_tempTextureHolder.data();
			gfxContext.init2DTexture(params);
		}
		if (mipLevel == _pTexture->max_level)
			break;
		++mipLevel;
		const u32 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;
		tmptex.format = mipTile.format;
		tmptex.size = mipTile.size;
		TileSizes sizes;
		_calcTileSizes(tileMipLevel, sizes, nullptr);
		tmptex.clampWidth = sizes.clampWidth;
		tmptex.clampHeight = sizes.clampHeight;
		// Insure mip-map levels size consistency.
		if (tmptex.width > 1)
			tmptex.width >>= 1;
		if (tmptex.height > 1)
			tmptex.height >>= 1;
		_pTexture->textureBytes += (tmptex.width * tmptex.height) << sizeShift;
	}
	if (m_curUnpackAlignment > 1)
		gfxContext.setTextureUnpackAlignment(m_curUnpackAlignment);
}

void TextureCache::_loadAccurate(u32 _tile, CachedTexture *_pTexture)
{
	u64 ricecrc = 0;
	u64 strongcrc = 0;
	if (_loadHiresTexture(_tile, _pTexture, ricecrc, strongcrc))
		return;

	bool force32bitFormat = false;
	_pTexture->max_level = 0;

	if (currentCombiner()->usesLOD() && gSP.texture.level > 1 && _tile > 0) {
		_pTexture->max_level = gDP.otherMode.textureDetail == G_TD_DETAIL ?
			static_cast<u8>(gSP.texture.level) :
			static_cast<u8>(gSP.texture.level - 1);
		force32bitFormat = _pTexture->max_level > 0;
	}

	u32 sizeShift = 1;
	{
		const TextureLoadParameters & loadParams =
			ImageFormat::get().tlp[gDP.otherMode.textureLUT][_pTexture->size][_pTexture->format];
		if (force32bitFormat || loadParams.autoFormat == internalcolorFormat::RGBA8)
			sizeShift = 2;
	}
	_pTexture->textureBytes = (_pTexture->width * _pTexture->height) << sizeShift;

	unsigned int totalTexSize = std::max(static_cast<u32>(_pTexture->textureBytes/sizeof(u32) + 8), MIPMAP_TILE_WIDTH)
								* (_pTexture->max_level + 1);

	if (m_tempTextureHolder.size() < totalTexSize) {
		m_tempTextureHolder.resize(totalTexSize);
	}

	GetTexelFunc GetTexel;
	InternalColorFormatParam glInternalFormat;
	DatatypeParam glType;

	auto getLoadParams = [&](u16 _format, u16 _size)
	{
		const TextureLoadParameters & loadParams =
			ImageFormat::get().tlp[gDP.otherMode.textureLUT][_size][_format];
		if (force32bitFormat || loadParams.autoFormat == internalcolorFormat::RGBA8) {
			GetTexel = loadParams.Get32;
			glInternalFormat = loadParams.glInternalFormat32;
			glType = loadParams.glType32;
		}
		else {
			GetTexel = loadParams.Get16;
			glInternalFormat = loadParams.glInternalFormat16;
			glType = loadParams.glType16;
		}
	};

	CachedTexture tmptex = *_pTexture;
	u16 line = tmptex.line;

	if (_pTexture->max_level > 0)
	{
		u32 mipLevel = 0;
		u32 texDataOffset = 8; // number of gDP.tiles

		// Load all tiles into one 1D texture atlas.
		while (true)
		{

			u32 mipRatioS = gDP.tiles[gSP.texture.tile + mipLevel + 1].shifts + 5u;
			if (mipRatioS >= 16u) mipRatioS -= 16u;
			u32 mipRatioT = gDP.tiles[gSP.texture.tile + mipLevel + 1].shiftt + 5u;
			if (mipRatioT >= 16) mipRatioT -= 16u;
			const u32 tileSizePacked = texDataOffset | (tmptex.width << 16) | (mipRatioT << 24) | (mipRatioS << 28);
			m_tempTextureHolder[mipLevel] = tileSizePacked;

			getLoadParams(tmptex.format, tmptex.size);
			_getTextureDestData(tmptex, &m_tempTextureHolder[texDataOffset], glInternalFormat, GetTexel, &line);

			if ((m_toggleDumpTex &&
				config.textureFilter.txHiresEnable != 0 &&
				config.hotkeys.enabledKeys[Config::HotKey::hkTexDump] != 0) ||
				config.textureFilter.txDump) {
				config.textureFilter.txStrongCRC ?
					txfilter_dmptx_strong((u8*)(m_tempTextureHolder.data() + texDataOffset), tmptex.width, tmptex.height,
						tmptex.width, (u16)u32(glInternalFormat),
						N64FormatSize(_pTexture->format, _pTexture->size),
						strongcrc) :
					txfilter_dmptx((u8*)(m_tempTextureHolder.data() + texDataOffset), tmptex.width, tmptex.height,
						tmptex.width, (u16)u32(glInternalFormat),
						N64FormatSize(_pTexture->format, _pTexture->size),
						ricecrc);
			}

			texDataOffset += tmptex.width * tmptex.height;
			if (mipLevel == _pTexture->max_level)
				break;
			++mipLevel;
			const u32 tileMipLevel = gSP.texture.tile + mipLevel + 1;
			gDPTile & mipTile = gDP.tiles[tileMipLevel];
			gDPTile & prevMipTile = gDP.tiles[tileMipLevel - 1];
			line = mipTile.line;
			tmptex.tMem = mipTile.tmem;
			tmptex.palette = mipTile.palette;
			tmptex.maskS = mipTile.masks;
			tmptex.maskT = mipTile.maskt;
			tmptex.format = mipTile.format;
			tmptex.size = mipTile.size;
			TileSizes sizes;
			_calcTileSizes(tileMipLevel, sizes, nullptr);
			tmptex.width = std::min(tmptex.width, static_cast<u16>(sizes.width));
			tmptex.height = std::min(tmptex.height, static_cast<u16>(sizes.height));
			tmptex.clampWidth = sizes.clampWidth;
			tmptex.clampHeight = sizes.clampHeight;
			_pTexture->textureBytes += (tmptex.width * tmptex.height) << sizeShift;
		}

		Context::InitTextureParams params;
		params.handle = _pTexture->name;
		params.textureUnitIndex = textureIndices::Tex[_tile];
		params.mipMapLevel = 0;
		params.mipMapLevels =1;
		params.msaaLevel = 0;
		params.width = std::min(texDataOffset, MIPMAP_TILE_WIDTH);
		params.height = (texDataOffset / MIPMAP_TILE_WIDTH) + ((texDataOffset % MIPMAP_TILE_WIDTH) ? 1 : 0);
		params.internalFormat = gfxContext.convertInternalTextureFormat(u32(glInternalFormat));
		params.format = colorFormat::RGBA;
		params.dataType = glType;
		params.data = m_tempTextureHolder.data();
		gfxContext.init2DTexture(params);
		_pTexture->mipmapAtlasWidth = params.width;
		_pTexture->mipmapAtlasHeight = params.height;
	}
	else
	{
		getLoadParams(tmptex.format, tmptex.size);
		_getTextureDestData(tmptex, m_tempTextureHolder.data(), glInternalFormat, GetTexel, &line);

		if ((config.generalEmulation.hacks&hack_LoadDepthTextures) != 0 && gDP.colorImage.address == gDP.depthImageAddress) {
			_loadDepthTexture(_pTexture, (u16*)m_tempTextureHolder.data());
			return;
		}

		if ((m_toggleDumpTex &&
			config.textureFilter.txHiresEnable != 0 &&
			config.hotkeys.enabledKeys[Config::HotKey::hkTexDump] != 0) ||
			config.textureFilter.txDump) {
				config.textureFilter.txStrongCRC ?
				txfilter_dmptx_strong((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
					tmptex.width, (u16)u32(glInternalFormat),
					N64FormatSize(_pTexture->format, _pTexture->size),
					strongcrc) :
				txfilter_dmptx((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
					tmptex.width, (u16)u32(glInternalFormat),
					N64FormatSize(_pTexture->format, _pTexture->size),
					ricecrc);
		}

		bool bLoaded = false;
		bool needEnhance = (config.textureFilter.txEnhancementMode | config.textureFilter.txFilterMode) != 0 &&
			_pTexture->max_level == 0 &&
			TFH.isInited();
		if (needEnhance) {
			if (config.textureFilter.txFilterIgnoreBG != 0) {
				switch (GBI.getMicrocodeType()) {
				case S2DEX_1_07:
				case S2DEX_1_03:
				case S2DEX_1_05:
					needEnhance = RSP.cmd != 0x01 && RSP.cmd != 0x02;
					break;
				case S2DEX2:
					needEnhance = RSP.cmd != 0x09 && RSP.cmd != 0x0A;
					break;
				}
			}
		}

		if (needEnhance) {
			GHQTexInfo ghqTexInfo;
			if (txfilter_filter((u8*)m_tempTextureHolder.data(), tmptex.width, tmptex.height,
							(u16)u32(glInternalFormat), (uint64)_pTexture->crc,
							N64FormatSize(_pTexture->format, _pTexture->size),
							&ghqTexInfo) != 0 && ghqTexInfo.data != nullptr) {
				if (ghqTexInfo.width % 2 != 0 &&
					ghqTexInfo.format != u32(internalcolorFormat::RGBA8) &&
					m_curUnpackAlignment > 1)
					gfxContext.setTextureUnpackAlignment(2);
				ghqTexInfo.format = gfxContext.convertInternalTextureFormat(ghqTexInfo.format);
				Context::InitTextureParams params;
				params.handle = _pTexture->name;
				params.textureUnitIndex = textureIndices::Tex[_tile];
				params.mipMapLevel = 0;
				params.msaaLevel = 0;
				params.width = ghqTexInfo.width;
				params.height = ghqTexInfo.height;
				params.internalFormat = InternalColorFormatParam(ghqTexInfo.format);
				params.format = ColorFormatParam(ghqTexInfo.texture_format);
				params.dataType = DatatypeParam(ghqTexInfo.pixel_type);
				params.data = ghqTexInfo.data;
				gfxContext.init2DTexture(params);
				_updateCachedTexture(ghqTexInfo, _pTexture, tmptex.width, tmptex.height);
				bLoaded = true;
			}
		}
		if (!bLoaded) {
			if (tmptex.width % 2 != 0 &&
				glInternalFormat != internalcolorFormat::RGBA8 &&
				m_curUnpackAlignment > 1)
				gfxContext.setTextureUnpackAlignment(2);
			Context::InitTextureParams params;
			params.handle = _pTexture->name;
			params.textureUnitIndex = textureIndices::Tex[_tile];
			params.mipMapLevel = 0;
			params.mipMapLevels = 1;
			params.msaaLevel = 0;
			params.width = tmptex.width;
			params.height = tmptex.height;
			params.internalFormat = gfxContext.convertInternalTextureFormat(u32(glInternalFormat));
			params.format = colorFormat::RGBA;
			params.dataType = glType;
			params.data = m_tempTextureHolder.data();
			gfxContext.init2DTexture(params);
		}
	}
	if (m_curUnpackAlignment > 1)
		gfxContext.setTextureUnpackAlignment(m_curUnpackAlignment);
}

struct TextureParams
{
	u16 width;
	u16 height;
	u32 flags;
};

static
u64 _calculateCRC(u32 _t, const TextureParams & _params, u32 _bytes)
{
	const bool rgba32 = gSP.textureTile[_t]->size == G_IM_SIZ_32b;
	if (_bytes == 0) {
		const u32 lineBytes = gSP.textureTile[_t]->line << 3;
		_bytes = _params.height*lineBytes;
	}
	if (rgba32)
		_bytes >>= 1;
	const u32 tMemMask = (gDP.otherMode.textureLUT == G_TT_NONE && !rgba32) ? 0x1FF : 0xFF;
	const u32 tMem = gSP.textureTile[_t]->tmem & tMemMask;
	const u64 *src = (u64*)&TMEM[tMem];
	const u32 maxBytes = (tMemMask + 1) << 3;
	const u32 tileTmemInBytes = tMem << 3;
	if (!rgba32 && (tileTmemInBytes + _bytes > maxBytes))
		_bytes = maxBytes - tileTmemInBytes;
	u64 crc = UINT64_MAX;
	crc = CRC_Calculate(crc, src, _bytes);

	if (rgba32) {
		src = (u64*)&TMEM[(gSP.textureTile[_t]->tmem + 256) & 0x1FF];
		crc = CRC_Calculate(crc, src, _bytes);
	}

	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], sizeof(u64) );
		else if (gSP.textureTile[_t]->size == G_IM_SIZ_8b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC256, sizeof(u64) );
	}

	if (config.generalEmulation.enableLOD != 0 && gSP.texture.level > 1 && _t > 0)
		crc = CRC_Calculate(crc, &gSP.texture.level, 4);

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

	return crc;
}

void TextureCache::activateTexture(u32 _t, CachedTexture *_pTexture)
{

	Context::TexParameters params;
	params.handle = _pTexture->name;
	if (config.video.multisampling > 0 && _pTexture->frameBufferTexture == CachedTexture::fbMultiSample) {
		params.target = textureTarget::TEXTURE_2D_MULTISAMPLE;
		params.textureUnitIndex = textureIndices::MSTex[_t];
	} else {
		params.target = textureTarget::TEXTURE_2D;
		params.textureUnitIndex = textureIndices::Tex[_t];
		params.minFilter = textureParameters::FILTER_NEAREST;
		params.magFilter = textureParameters::FILTER_NEAREST;
		params.maxMipmapLevel = Parameter(0);

		if (config.generalEmulation.enableInaccurateTextureCoordinates != 0) {

			const bool bUseBilinear = gDP.otherMode.textureFilter != G_TF_POINT && config.texture.bilinearMode != BILINEAR_3POINT;
			const bool bUseLOD = currentCombiner()->usesLOD();
			const s32 texLevel = bUseLOD ? _pTexture->max_level : 0;
			params.maxMipmapLevel = Parameter(texLevel);

			if (bUseLOD) {
				if (bUseBilinear) {
					// Apply standard bilinear to mipmap textures
					if (texLevel > 0)
						params.minFilter = textureParameters::FILTER_LINEAR_MIPMAP_NEAREST;
					else
						params.minFilter = textureParameters::FILTER_LINEAR;
					params.magFilter = textureParameters::FILTER_LINEAR;
				} else {
					if (texLevel > 0)
						params.minFilter = textureParameters::FILTER_NEAREST_MIPMAP_NEAREST;
					else
						params.minFilter = textureParameters::FILTER_NEAREST;
					params.magFilter = textureParameters::FILTER_NEAREST;
				}
			}

			// Set clamping modes
			params.wrapS = _pTexture->clampS ? textureParameters::WRAP_CLAMP_TO_EDGE :
				_pTexture->mirrorS ? textureParameters::WRAP_MIRRORED_REPEAT : textureParameters::WRAP_REPEAT;
			params.wrapT = _pTexture->clampT ? textureParameters::WRAP_CLAMP_TO_EDGE :
				_pTexture->mirrorT ? textureParameters::WRAP_MIRRORED_REPEAT : textureParameters::WRAP_REPEAT;
		}

		if (config.texture.anisotropy != 0) {
			switch (dwnd().getDrawer().getDrawingState()) {
				case DrawingState::Triangle:
				case DrawingState::ScreenSpaceTriangle:
					params.maxAnisotropy = Parameter(static_cast<f32>(config.texture.anisotropy));
					break;
				default:
					break;
			}
		}
	}

	gfxContext.setTextureParameters(params);

	current[_t] = _pTexture;
}

void TextureCache::activateDummy(u32 _t)
{
	Context::TexParameters params;
	params.handle = m_pDummy->name;
	params.target = textureTarget::TEXTURE_2D;
	params.textureUnitIndex = textureIndices::Tex[_t];
	params.minFilter = textureParameters::FILTER_NEAREST;
	params.magFilter = textureParameters::FILTER_NEAREST;
	gfxContext.setTextureParameters(params);
}

void TextureCache::activateMSDummy(u32 _t)
{
	Context::TexParameters params;
	params.handle = m_pMSDummy->name;
	params.target = textureTarget::TEXTURE_2D_MULTISAMPLE;
	params.textureUnitIndex = textureIndices::MSTex[_t];
	gfxContext.setTextureParameters(params);
}

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

	crc = CRC_Calculate( UINT64_MAX, &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], sizeof(u64) );
		else if (gSP.bgImage.size == G_IM_SIZ_8b)
			crc = CRC_Calculate( crc, &gDP.paletteCRC256, sizeof(u64) );
	}

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

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

		assert(currentTex.width == gSP.bgImage.width);
		assert(currentTex.height == gSP.bgImage.height);
		assert(currentTex.format == gSP.bgImage.format);
		assert(currentTex.size == gSP.bgImage.size);
		currentTex.clampS = gSP.bgImage.clampS;
		currentTex.clampT = gSP.bgImage.clampT;

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

	m_misses++;

	CachedTexture * pCurrent = _addTexture(crc);

	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 = gSP.bgImage.clampS;
	pCurrent->clampT = gSP.bgImage.clampT;
	pCurrent->line = 0;
	pCurrent->tMem = 0;
	pCurrent->frameBufferTexture = CachedTexture::fbNone;

	pCurrent->scaleS = 1.0f / (f32)(pCurrent->width);
	pCurrent->scaleT = 1.0f / (f32)(pCurrent->height);

	pCurrent->hdRatioS = 1.0f;
	pCurrent->hdRatioT = 1.0f;

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

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

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

	current[0] = pCurrent;
}

void TextureCache::clear()
{
	current[0] = current[1] = nullptr;

	for (auto cur = m_textures.cbegin(); cur != m_textures.cend(); ++cur) {
		gfxContext.deleteTexture(cur->name);
	}
	m_textures.clear();
	m_lruTextureLocations.clear();
	m_hdTexCacheSize = 0u;
}

void TextureCache::toggleDumpTex()
{
	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));
	}
}

void TextureCache::update(u32 _t)
{
	const gDPTile * pTile = gSP.textureTile[_t];
	switch (pTile->textureMode) {
	case TEXTUREMODE_BGIMAGE:
		_updateBackground();
		return;
	case TEXTUREMODE_FRAMEBUFFER:
		FrameBuffer_ActivateBufferTexture( _t, pTile->frameBufferAddress );
		return;
	case TEXTUREMODE_FRAMEBUFFER_BG:
		FrameBuffer_ActivateBufferTextureBG( _t, pTile->frameBufferAddress );
		return;
	}

	if (_t == 1 && needReplaceTex1ByTex0()) {
		current[1] = current[0];
		if (current[1] != nullptr) {
			activateTexture(1, current[1]);
			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];
		pTile = gSP.textureTile[_t];
	}

	TileSizes sizes;
	_calcTileSizes(_t, sizes, gDP.loadTile);
	TextureParams params;
	const u32 texLevel = _t == 0 ? 0U : gSP.texture.level;
	params.flags = pTile->masks	|
		(pTile->maskt   << 4)	|
		(pTile->mirrors << 8)	|
		(pTile->mirrort << 9)	|
		(pTile->clamps << 10)	|
		(pTile->clampt << 11)	|
		(pTile->size   << 12)	|
		(pTile->format << 14)	|
		(gDP.otherMode.textureLUT << 17) |
		(texLevel << 19);
	params.width = sizes.width;
	params.height = sizes.height;

	const u64 crc = _calculateCRC(_t, params, sizes.bytes);

	if (current[_t] != nullptr && 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 & currentTex = *iter;

		if (currentTex.width == sizes.width && currentTex.height == sizes.height) {
			m_textures.splice(m_textures.begin(), m_textures, iter);

			assert(currentTex.format == pTile->format);
			assert(currentTex.size == pTile->size);

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

		if (currentTex.bHDTexture)
			m_hdTexCacheSize -= currentTex.textureBytes;
		gfxContext.deleteTexture(currentTex.name);
		m_lruTextureLocations.erase(locations_iter);
		m_textures.erase(iter);
	}

	m_misses++;

	CachedTexture * pCurrent = _addTexture(crc);

	pCurrent->address = gDP.loadInfo[pTile->tmem].texAddress;

	pCurrent->format = pTile->format;
	pCurrent->size = pTile->size;

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

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

	pCurrent->palette = pTile->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 = pTile->masks;
	pCurrent->maskT = pTile->maskt;
	pCurrent->mirrorS = pTile->mirrors;
	pCurrent->mirrorT = pTile->mirrort;
	pCurrent->clampS = pTile->clamps;
	pCurrent->clampT = pTile->clampt;
	pCurrent->line = pTile->line;
	pCurrent->tMem = pTile->tmem;
	pCurrent->frameBufferTexture = CachedTexture::fbNone;

	pCurrent->scaleS = 1.0f / (pCurrent->maskS ? f32(pow2(pCurrent->width)) : f32(pCurrent->width));
	pCurrent->scaleT = 1.0f / (pCurrent->maskT ? f32(pow2(pCurrent->height)) : f32(pCurrent->height));

	pCurrent->hdRatioS = 1.0f;
	pCurrent->hdRatioT = 1.0f;

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

	if (config.generalEmulation.enableInaccurateTextureCoordinates) {
		_loadFast(_t, pCurrent);
	} else {
		_loadAccurate(_t, pCurrent);
	}
	activateTexture( _t, pCurrent );

	current[_t] = pCurrent;
}

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

	if (gDP.otherMode.textureLOD == G_TL_LOD && gSP.texture.level == 0 && !currentCombiner()->usesLOD())
		t = 0;

	shiftScaleS = calcShiftScaleS(tile);
	shiftScaleT = calcShiftScaleT(tile);
}

bool needReplaceTex1ByTex0()
{
	return config.generalEmulation.enableInaccurateTextureCoordinates &&
	   gSP.texture.level == 0 && gDP.otherMode.textureLOD == G_TL_LOD && gDP.otherMode.textureDetail == G_TD_CLAMP;
}

