/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
Copyright (C) 2016 Axel Gneiting

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

// gl_texmgr.c -- fitzquake's texture manager. manages texture images

#include "quakedef.h"
#include "gl_heap.h"

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#elif defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4505)
#endif

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC
// STB_IMAGERESIZE config:
// plug our Mem_Alloc in stb_image_resize:
// use comma operator to evaluate c, to avoid "unused parameter" warnings
#define STBIR_MALLOC(sz, c) ((void)(c), Mem_Alloc (sz))
#define STBIR_FREE(p, c)	((void)(c), Mem_Free (p))
#include "stb_image_resize.h"

#if defined(__GNUC__)
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif

static cvar_t gl_max_size = {"gl_max_size", "0", CVAR_NONE};
static cvar_t gl_picmip = {"gl_picmip", "0", CVAR_NONE};

extern cvar_t vid_filter;
extern cvar_t vid_anisotropic;

#define MAX_MIPS 16
static int			numgltextures;
static gltexture_t *active_gltextures, *free_gltextures;
gltexture_t		   *notexture, *nulltexture, *whitetexture, *greytexture, *greylightmap, *bluenoisetexture;

unsigned int d_8to24table[256];
unsigned int d_8to24table_fbright[256];
unsigned int d_8to24table_fbright_fence[256];
unsigned int d_8to24table_nobright[256];
unsigned int d_8to24table_nobright_fence[256];
unsigned int d_8to24table_conchars[256];

// Heap
#define TEXTURE_HEAP_MEMORY_SIZE_MB 64
#define TEXTURE_HEAP_PAGE_SIZE		16384

static glheap_t	 *texmgr_heap;
static SDL_Mutex *texmgr_mutex;

static byte bluenoise_data[4096] = {
	0x27, 0x62, 0x08, 0x4C, 0xDE, 0xBA, 0x05, 0xEF, 0x2A, 0xA1, 0xF7, 0x4A, 0x5F, 0x29, 0xE8, 0x34, 0xA9, 0xCB, 0x40, 0x60, 0xD5, 0x87, 0x70, 0xD0, 0x61, 0x8A,
	0xDF, 0xB2, 0xD8, 0xFA, 0x07, 0x74, 0x31, 0x56, 0x1A, 0x4B, 0xAA, 0x36, 0xD4, 0x16, 0x95, 0x2F, 0x68, 0x8E, 0x77, 0x25, 0x49, 0xE3, 0x12, 0x6C, 0x9F, 0xD7,
	0x7D, 0xE9, 0xB5, 0x63, 0x26, 0x76, 0x3A, 0x89, 0x47, 0xFA, 0x1D, 0x85, 0xE9, 0x9F, 0xD2, 0x7A, 0x1E, 0xA3, 0x56, 0xC4, 0xDB, 0x16, 0x0A, 0xE1, 0xBC, 0x88,
	0x73, 0xC4, 0x13, 0x24, 0xED, 0xB7, 0x98, 0x28, 0x1B, 0xB4, 0x3B, 0x10, 0x96, 0x19, 0x38, 0x50, 0x89, 0xD1, 0x99, 0xDD, 0x7C, 0x26, 0x71, 0xE1, 0x64, 0xAF,
	0x3F, 0xD0, 0x53, 0x1D, 0xFE, 0xC1, 0x98, 0xCB, 0x7B, 0x51, 0xFA, 0x5F, 0x16, 0x4C, 0x2F, 0xD9, 0x8F, 0xA3, 0xF0, 0xD2, 0x2A, 0x6E, 0x97, 0x4F, 0xB3, 0x6C,
	0x30, 0x90, 0xF3, 0x40, 0x73, 0x34, 0x91, 0x65, 0x81, 0x9A, 0x39, 0xAE, 0x02, 0x54, 0x80, 0x9F, 0x6D, 0x10, 0x39, 0xF4, 0x7F, 0x50, 0xFB, 0xCA, 0x46, 0x76,
	0xA5, 0xBE, 0x66, 0x0F, 0x43, 0xA5, 0xC4, 0x8E, 0xF2, 0xBE, 0x0B, 0xFB, 0x78, 0x9E, 0x09, 0xD8, 0xA6, 0x0E, 0x62, 0xEA, 0x3D, 0xBC, 0x26, 0xD0, 0x97, 0xC4,
	0x71, 0xFE, 0x43, 0xBA, 0x18, 0x5B, 0xAD, 0xE2, 0xC8, 0x0D, 0xD7, 0x18, 0xC0, 0x5A, 0xE3, 0x0D, 0xCA, 0xAA, 0x46, 0xB6, 0xEB, 0x51, 0x1F, 0xFE, 0xD5, 0x41,
	0xF2, 0xDC, 0x8D, 0x4D, 0xAB, 0xDA, 0xC5, 0x08, 0x69, 0xAB, 0xE1, 0x59, 0xF0, 0x22, 0xE4, 0xB4, 0xF7, 0x15, 0x69, 0x05, 0x3D, 0x54, 0x82, 0x20, 0x4C, 0xBA,
	0xEE, 0x6E, 0x44, 0x33, 0x8B, 0x18, 0xAE, 0x09, 0x87, 0x35, 0xA9, 0x0D, 0x85, 0x58, 0x01, 0xE4, 0x69, 0x83, 0x08, 0x33, 0x7C, 0x3D, 0x8A, 0xFC, 0x45, 0xAB,
	0x28, 0x67, 0x87, 0xFB, 0x21, 0xD6, 0x31, 0x70, 0xCC, 0x5D, 0x90, 0xA6, 0x61, 0x30, 0x06, 0xC0, 0x5C, 0x74, 0x2E, 0x8C, 0x9B, 0x25, 0x81, 0x31, 0x05, 0x92,
	0x4A, 0x7F, 0x34, 0x5A, 0xE7, 0xD3, 0xA1, 0x2C, 0x93, 0xC8, 0xE7, 0x29, 0x5B, 0x81, 0xB3, 0xCD, 0xF3, 0x74, 0x59, 0xDB, 0xF2, 0x47, 0x69, 0xEE, 0x24, 0xB0,
	0x99, 0x35, 0xCC, 0x4E, 0xBF, 0x9D, 0xF2, 0x57, 0xA4, 0x74, 0x00, 0x7E, 0x97, 0xD0, 0x13, 0x55, 0x7B, 0x04, 0xC2, 0xA2, 0x12, 0x7D, 0x2A, 0x18, 0xBA, 0x76,
	0xD0, 0xFC, 0x1F, 0xA2, 0xEE, 0x41, 0xE6, 0x16, 0xC2, 0xB3, 0x6C, 0xD6, 0x9F, 0xCA, 0x26, 0xAF, 0x85, 0x49, 0xB9, 0x6F, 0xD9, 0xA7, 0x12, 0x8A, 0x38, 0x99,
	0x04, 0xDF, 0x4E, 0xA1, 0x1E, 0x95, 0xC7, 0x77, 0xB8, 0xDD, 0x3E, 0xD4, 0x77, 0x1E, 0xF4, 0x91, 0xDA, 0x11, 0x64, 0x24, 0xCB, 0x37, 0xDA, 0xBC, 0xF0, 0x4B,
	0xB4, 0xDD, 0x9E, 0x8C, 0x61, 0xEF, 0x3E, 0xB4, 0xE3, 0x4B, 0xE9, 0x9B, 0x3B, 0x86, 0x49, 0x14, 0x65, 0xBA, 0x55, 0xD4, 0x61, 0xF9, 0x88, 0x3F, 0x19, 0x60,
	0x71, 0xF1, 0x98, 0x1E, 0x0E, 0xF8, 0x5E, 0x43, 0x68, 0xE1, 0xC3, 0xF9, 0x21, 0x67, 0x2C, 0xBB, 0x42, 0x64, 0x2A, 0x04, 0x55, 0x17, 0xA0, 0x64, 0xC5, 0x49,
	0xA8, 0x28, 0x71, 0x42, 0xB7, 0xEB, 0x95, 0x52, 0x12, 0x61, 0x32, 0x1C, 0x6F, 0x3B, 0x29, 0xF6, 0x45, 0x0B, 0xDC, 0x96, 0x6B, 0xC7, 0x0D, 0x23, 0x6A, 0xB5,
	0xE2, 0x93, 0xCF, 0x7C, 0x02, 0x36, 0xA1, 0x4C, 0x0D, 0xEC, 0xBD, 0xDE, 0x02, 0x3A, 0xD0, 0x56, 0xE0, 0x7E, 0x34, 0x02, 0xB5, 0x75, 0x15, 0x55, 0xAC, 0x7C,
	0xC9, 0x88, 0xFC, 0xA7, 0xE9, 0x83, 0x92, 0xFA, 0x2F, 0x88, 0xEC, 0x14, 0x80, 0x5E, 0xFA, 0xAB, 0x85, 0x1A, 0xE3, 0x70, 0xB0, 0xE9, 0x8E, 0xA2, 0xC5, 0xE5,
	0x5B, 0xAC, 0x75, 0xBC, 0x85, 0x33, 0x57, 0xF9, 0x8B, 0xAE, 0x54, 0xF0, 0x0B, 0x35, 0xAA, 0x26, 0xF6, 0x8F, 0x75, 0xCE, 0x29, 0x7B, 0x54, 0xA7, 0x8A, 0xB6,
	0x78, 0x2A, 0xAA, 0xC5, 0x9C, 0xEC, 0xD1, 0xA2, 0x46, 0xD5, 0x92, 0x3C, 0xE5, 0x19, 0x0C, 0xD2, 0x38, 0xB3, 0xCE, 0x4B, 0xBF, 0x58, 0x06, 0xB6, 0xE0, 0x3A,
	0x0C, 0xC7, 0x2E, 0x49, 0x07, 0xC0, 0x81, 0x21, 0x43, 0xF8, 0x7A, 0x0F, 0x94, 0x1A, 0xCE, 0x4F, 0x21, 0xA9, 0x15, 0x78, 0x40, 0xDA, 0x2D, 0x7E, 0xC1, 0x71,
	0x5A, 0xDE, 0xB1, 0x43, 0xEA, 0x1C, 0xAD, 0x95, 0x33, 0xFE, 0x48, 0x14, 0x66, 0xF4, 0x41, 0x8B, 0x1B, 0x50, 0x26, 0x83, 0x2F, 0xEE, 0x0F, 0x5D, 0x9F, 0x70,
	0x4A, 0x7B, 0x5A, 0x21, 0x10, 0x73, 0xE2, 0x25, 0x6E, 0x8F, 0xD0, 0x9E, 0x79, 0x57, 0xD5, 0x68, 0xFF, 0x38, 0xD1, 0xA8, 0x56, 0x03, 0x65, 0xD3, 0xB6, 0x37,
	0xEA, 0x66, 0xF5, 0xD6, 0xC2, 0x9E, 0x00, 0xCA, 0x60, 0x1C, 0xD4, 0x9A, 0x4A, 0x85, 0x11, 0x6C, 0xBC, 0x57, 0xE2, 0xC5, 0x6E, 0x20, 0xCB, 0xE4, 0x94, 0xBE,
	0x08, 0x5C, 0x73, 0xFC, 0x91, 0x61, 0xC1, 0x6D, 0xB8, 0xF5, 0x33, 0xDA, 0xC0, 0x94, 0xE3, 0x68, 0xA4, 0x3E, 0x97, 0xAF, 0xF4, 0x32, 0x4F, 0x1F, 0xBD, 0xEF,
	0x8B, 0xA1, 0x1B, 0x5D, 0x97, 0x2D, 0xE0, 0xC2, 0x8A, 0x4B, 0x27, 0x81, 0x9D, 0x05, 0x90, 0x2C, 0x5F, 0x48, 0xEF, 0x92, 0xA5, 0xFA, 0x3E, 0x06, 0xEC, 0x20,
	0xC9, 0x30, 0x99, 0x09, 0x3E, 0x63, 0x0E, 0xA1, 0x80, 0x30, 0x51, 0xD5, 0x24, 0xE8, 0xB0, 0xC8, 0x3F, 0x19, 0xE4, 0x07, 0x89, 0x25, 0x52, 0xA9, 0x00, 0x2D,
	0xB9, 0xF7, 0xD3, 0x80, 0x0A, 0xCA, 0x44, 0xA5, 0x68, 0xE7, 0x02, 0x3E, 0x26, 0xB3, 0x7D, 0x47, 0xED, 0x73, 0x15, 0xAE, 0x3C, 0xDC, 0xFC, 0x59, 0xC7, 0x42,
	0x70, 0xAD, 0x80, 0xDF, 0x24, 0x6F, 0x4E, 0x86, 0xB0, 0x66, 0xBD, 0xA3, 0xFC, 0x5F, 0xDB, 0x7F, 0xF5, 0x8B, 0xD6, 0xEC, 0x5C, 0x11, 0xA6, 0x84, 0x6C, 0x9D,
	0x32, 0x11, 0xD8, 0xA9, 0x97, 0x49, 0xAF, 0xCE, 0x81, 0x65, 0xEF, 0x89, 0x44, 0x16, 0x54, 0x30, 0xEB, 0x5F, 0x7B, 0x11, 0xD8, 0x84, 0x99, 0x72, 0x51, 0xE0,
	0x93, 0xC8, 0x09, 0x65, 0xF5, 0x7F, 0x9A, 0x0A, 0x6D, 0xA6, 0x16, 0xD8, 0xE7, 0x0E, 0x38, 0xB5, 0x13, 0xBE, 0x33, 0x0E, 0xE6, 0x2A, 0x53, 0x78, 0x8E, 0x46,
	0xA9, 0x26, 0xB6, 0x4D, 0x36, 0xB0, 0x72, 0xC0, 0xF8, 0x3B, 0xB6, 0x4B, 0xE0, 0x64, 0x7E, 0x53, 0xF7, 0x37, 0x76, 0xE0, 0x12, 0x3E, 0xCA, 0x20, 0x76, 0x9D,
	0xB0, 0xDD, 0x8A, 0x1E, 0xC3, 0xFD, 0x2B, 0x5B, 0xB8, 0xF7, 0xCF, 0x0F, 0x2B, 0xB8, 0xA1, 0xD6, 0x4D, 0x32, 0xBE, 0x20, 0xE9, 0xB4, 0x31, 0x7D, 0xBD, 0x4D,
	0xFF, 0x8D, 0x58, 0xEB, 0xD2, 0x7A, 0xC7, 0x96, 0xE0, 0x19, 0x39, 0x01, 0xCE, 0x6F, 0x1B, 0xC4, 0x06, 0x98, 0x25, 0x44, 0xDC, 0x01, 0x1D, 0xF2, 0x8A, 0x09,
	0xBA, 0x2A, 0x6B, 0xC5, 0x1E, 0x59, 0xFF, 0x97, 0xB3, 0xE5, 0x5E, 0xC5, 0x05, 0x6C, 0x4C, 0xB9, 0x3A, 0x91, 0xAC, 0x49, 0x17, 0x33, 0xA4, 0x6B, 0xE7, 0x86,
	0x3B, 0x22, 0x90, 0xCD, 0x5F, 0x78, 0x43, 0x91, 0x53, 0x63, 0x23, 0x9C, 0xCA, 0x75, 0xA7, 0x3F, 0x9B, 0x5D, 0x1F, 0x41, 0x6B, 0xD5, 0xBA, 0xF1, 0x52, 0xE7,
	0x9F, 0x77, 0xFB, 0x55, 0xE9, 0x91, 0x7C, 0x60, 0x98, 0xC3, 0x74, 0x42, 0xCF, 0xA1, 0xED, 0x03, 0x8E, 0xA3, 0x2E, 0x6E, 0x09, 0x4E, 0x35, 0xFA, 0x92, 0x27,
	0xF0, 0xA0, 0x74, 0x04, 0xD2, 0xE4, 0x8C, 0xC4, 0x5F, 0x42, 0x1D, 0x75, 0xFB, 0x58, 0xB2, 0x17, 0xF1, 0xA1, 0xC7, 0x0B, 0xDB, 0x87, 0xF1, 0x07, 0x67, 0x2A,
	0x1A, 0xDB, 0x03, 0xB7, 0x8A, 0xF9, 0xAE, 0x0F, 0x7F, 0x94, 0x64, 0x2F, 0x89, 0x3E, 0xD0, 0x18, 0x69, 0xC9, 0x2E, 0xAF, 0xD3, 0x55, 0x25, 0x93, 0xE6, 0x1A,
	0x84, 0x60, 0xD9, 0xBB, 0x48, 0xD3, 0x87, 0xA7, 0xD9, 0x7D, 0x40, 0xD2, 0x62, 0x0F, 0xE0, 0x55, 0x69, 0x21, 0x77, 0x0B, 0xEE, 0xD8, 0xAB, 0x07, 0xC2, 0xDF,
	0x71, 0x02, 0xE3, 0x50, 0x2A, 0xFA, 0xBA, 0x3B, 0xAC, 0xD1, 0x45, 0x84, 0xF7, 0x50, 0x6E, 0xEF, 0x2F, 0x4B, 0x9D, 0x5B, 0x25, 0xC7, 0xAF, 0x0C, 0xDD, 0x5E,
	0xA9, 0x81, 0xB8, 0x0C, 0x4E, 0xF0, 0x13, 0x38, 0xFD, 0xAE, 0x5B, 0x32, 0x4D, 0xB1, 0x3A, 0xF3, 0x7A, 0x18, 0xC0, 0x66, 0x23, 0x12, 0xBC, 0xAB, 0x84, 0x33,
	0xC8, 0xA6, 0x40, 0xF3, 0xB3, 0x9C, 0x82, 0x52, 0x91, 0x63, 0x34, 0x48, 0xA7, 0x8B, 0x3D, 0x65, 0x80, 0x1C, 0x6E, 0x95, 0x14, 0x59, 0xE5, 0xC1, 0x91, 0xAF,
	0xCB, 0x81, 0x0B, 0xDA, 0x74, 0xED, 0x36, 0x44, 0xFE, 0x1C, 0xBE, 0x4A, 0x28, 0xD9, 0x38, 0xE4, 0xA3, 0x71, 0x85, 0x9F, 0x6B, 0x0B, 0xDB, 0x78, 0xC9, 0x9B,
	0x24, 0x0D, 0x57, 0xE7, 0x2B, 0xF2, 0xA0, 0x5A, 0xEA, 0x4A, 0x19, 0xFB, 0x95, 0x29, 0x87, 0xC0, 0x4E, 0x38, 0x26, 0xCC, 0x13, 0xF3, 0x9B, 0x80, 0x26, 0xD0,
	0xB6, 0x9A, 0xD4, 0xA8, 0x4A, 0xEE, 0x2D, 0x7A, 0xA3, 0x37, 0x22, 0x11, 0x61, 0x3B, 0xA5, 0xC0, 0x17, 0xCC, 0x86, 0xA4, 0x6A, 0x79, 0x8F, 0xF4, 0x04, 0x99,
	0x63, 0x8B, 0x43, 0x20, 0xE0, 0xCC, 0x49, 0xBB, 0x8A, 0x18, 0xF8, 0x6E, 0xE4, 0x82, 0x98, 0xAE, 0x3F, 0x90, 0x77, 0xCC, 0x08, 0x6F, 0xB7, 0x5C, 0x79, 0xE6,
	0x17, 0x62, 0x06, 0xFC, 0x6F, 0xB6, 0x2E, 0xD3, 0xE7, 0x19, 0x6A, 0xF4, 0x0D, 0x31, 0xE8, 0x05, 0x5E, 0xC8, 0xDD, 0xB5, 0x04, 0x6B, 0xD9, 0x48, 0xE8, 0x95,
	0x27, 0x55, 0x66, 0xE3, 0x05, 0x51, 0xD3, 0xE4, 0x35, 0xB3, 0x70, 0xED, 0x12, 0xC5, 0xFA, 0xB2, 0x03, 0x62, 0x28, 0xED, 0x3F, 0xA7, 0x07, 0xB9, 0x45, 0xD0,
	0x64, 0xC7, 0x03, 0x4E, 0xDE, 0x37, 0x8B, 0x9B, 0xD8, 0x1F, 0x3A, 0xB0, 0xCD, 0xD9, 0x7E, 0xA4, 0xE1, 0x44, 0x5C, 0x78, 0x4F, 0xAE, 0xC7, 0x59, 0x46, 0x76,
	0xBE, 0x88, 0x38, 0x73, 0x1E, 0x8D, 0x51, 0xF4, 0x9B, 0xB8, 0x78, 0xD1, 0xFB, 0xB0, 0x8F, 0x2E, 0xB9, 0x95, 0x10, 0x26, 0x59, 0x9E, 0xCD, 0x52, 0x7D, 0x2D,
	0x59, 0x75, 0x36, 0xC1, 0x92, 0x7C, 0xD4, 0x57, 0x67, 0x2C, 0x8D, 0x11, 0x34, 0xFC, 0x70, 0xBB, 0x1C, 0xF8, 0x2A, 0x43, 0xC2, 0xF3, 0x50, 0x6C, 0x0E, 0x97,
	0x57, 0x1D, 0xC4, 0x8B, 0xA0, 0xBE, 0x0C, 0x3A, 0x86, 0xD9, 0x96, 0x22, 0xF8, 0x50, 0xAE, 0x9D, 0xFD, 0x41, 0xC3, 0x82, 0x2B, 0x58, 0x1B, 0x01, 0x6D, 0x45,
	0x1E, 0x7B, 0xF6, 0x3D, 0xAC, 0xC4, 0x83, 0x14, 0x40, 0x23, 0xBC, 0xAA, 0xD7, 0x9B, 0xF1, 0x4F, 0xAC, 0x1A, 0x33, 0x96, 0xDE, 0xF2, 0x50, 0xAB, 0xE2, 0x22,
	0x81, 0xA4, 0x58, 0xAF, 0x7B, 0x65, 0xA8, 0x03, 0x8D, 0xA0, 0xEE, 0x46, 0x32, 0xEA, 0x69, 0x01, 0x25, 0xDE, 0x93, 0xFE, 0x2C, 0x13, 0xA6, 0x61, 0xDE, 0x15,
	0xCF, 0x29, 0x0A, 0x61, 0xD2, 0x0E, 0xEB, 0xAB, 0x8C, 0xBD, 0x34, 0xE5, 0x9F, 0xD8, 0x5C, 0x6E, 0xEE, 0x49, 0x67, 0xF7, 0xD6, 0x8C, 0xE5, 0x48, 0x09, 0x1C,
	0x82, 0xCE, 0x0E, 0xF9, 0x70, 0xC6, 0x1F, 0xA1, 0x79, 0xC2, 0x5C, 0x9C, 0x44, 0xD7, 0x0A, 0xC8, 0xEC, 0x14, 0xE0, 0x7F, 0x2E, 0xC6, 0x20, 0xBA, 0x88, 0xAD,
	0x3F, 0xF7, 0xB4, 0x49, 0x62, 0xC3, 0x6F, 0xE6, 0xB8, 0x7B, 0x3F, 0x92, 0x6A, 0xBC, 0xE7, 0x7B, 0xA5, 0x36, 0x67, 0xDF, 0x3F, 0xF1, 0x81, 0xC6, 0x4F, 0x08,
	0xCB, 0x19, 0x8A, 0xDD, 0x00, 0xB5, 0xA4, 0x18, 0x72, 0x64, 0x90, 0xE9, 0x3D, 0x6A, 0x5B, 0xE2, 0xB5, 0x48, 0x83, 0x02, 0xD2, 0x3C, 0x16, 0x89, 0xEF, 0x2F,
	0x6B, 0x94, 0x4B, 0x27, 0x59, 0xD0, 0x3E, 0xFE, 0x63, 0x78, 0xD6, 0x14, 0xCD, 0x72, 0x35, 0x1B, 0xAA, 0x7E, 0x04, 0x54, 0x33, 0xC9, 0x09, 0xF1, 0x83, 0x55,
	0x45, 0x95, 0x19, 0xBF, 0x4D, 0x75, 0x23, 0xA3, 0x60, 0x14, 0x96, 0xB4, 0x38, 0xA7, 0x29, 0x9B, 0x7A, 0x30, 0x5F, 0x3B, 0xFD, 0xCB, 0x2E, 0xB9, 0xA7, 0x23,
	0x89, 0x9A, 0x0A, 0x38, 0xEA, 0x63, 0x28, 0xF6, 0xB2, 0x66, 0x0E, 0xBD, 0xE4, 0x3C, 0x87, 0xB9, 0x9A, 0x71, 0xB1, 0x53, 0x06, 0xE3, 0x29, 0x5D, 0x9A, 0x82,
	0xED, 0xD1, 0xE2, 0x40, 0x8C, 0xF3, 0x99, 0x4A, 0xAB, 0x21, 0xD4, 0x31, 0xB3, 0xDB, 0x26, 0xFA, 0x90, 0xCD, 0x09, 0x54, 0xD2, 0x2C, 0x6A, 0xFD, 0x76, 0xE9,
	0x56, 0xBF, 0x44, 0xEB, 0xC5, 0x85, 0x0F, 0x9C, 0x56, 0xD9, 0x4B, 0xF5, 0xC2, 0x2C, 0x54, 0xAA, 0x8E, 0xBC, 0x4D, 0x96, 0x75, 0xCA, 0x51, 0x9F, 0x78, 0x1F,
	0xD3, 0xF7, 0x0E, 0xE8, 0x1A, 0x86, 0xA8, 0x93, 0x4A, 0xF4, 0x09, 0x53, 0x90, 0x0E, 0x5A, 0xA0, 0x24, 0xCD, 0x18, 0x6D, 0xE8, 0x5C, 0x9F, 0x01, 0xF4, 0x6F,
	0x5E, 0x82, 0x10, 0xB7, 0x99, 0xF6, 0xB0, 0xDD, 0x8D, 0x1F, 0x4A, 0x85, 0x0B, 0xD3, 0x1D, 0x93, 0x51, 0xDE, 0x27, 0xAE, 0x7C, 0x02, 0x6F, 0x15, 0xD1, 0x79,
	0xDD, 0xFD, 0x6E, 0xD6, 0x12, 0xE5, 0x35, 0xDB, 0x26, 0xFD, 0x5D, 0x01, 0xAB, 0x64, 0x32, 0x47, 0xC0, 0xDE, 0x39, 0xCA, 0x6D, 0xA2, 0xB6, 0xC6, 0x67, 0x79,
	0x30, 0xF8, 0xB5, 0x60, 0xD9, 0x81, 0xBF, 0x3B, 0x75, 0x8A, 0x16, 0xCA, 0x3C, 0xA9, 0xD8, 0x44, 0x6D, 0x35, 0x7A, 0x05, 0x40, 0xC3, 0xE2, 0xB7, 0x66, 0xF9,
	0x71, 0xA7, 0x06, 0x67, 0xF3, 0xBF, 0x40, 0xEC, 0x96, 0x37, 0xA2, 0x61, 0x43, 0x14, 0x22, 0xA3, 0x5A, 0x86, 0x07, 0xA8, 0x45, 0x8E, 0xB6, 0xC9, 0x4E, 0x7C,
	0xA2, 0x8E, 0x68, 0x27, 0x7B, 0x0C, 0xE9, 0x31, 0x42, 0x21, 0xA6, 0xDC, 0xC0, 0x15, 0x73, 0x44, 0xA5, 0x0D, 0x28, 0xFD, 0xB0, 0xC4, 0x4C, 0x9A, 0xED, 0x57,
	0x2C, 0xE6, 0x1E, 0x88, 0x4D, 0xEF, 0xA4, 0x5F, 0x30, 0x16, 0x9C, 0x3D, 0x2C, 0xCD, 0xB5, 0x47, 0x76, 0x1A, 0x8C, 0x5E, 0xE0, 0xB3, 0x84, 0x07, 0xB8, 0xC8,
	0x7E, 0x30, 0x41, 0xF8, 0x70, 0xC3, 0x1A, 0x7F, 0x38, 0xE9, 0x2A, 0xDB, 0xEF, 0x06, 0xCF, 0xFA, 0x5B, 0xBD, 0x1C, 0xD3, 0x86, 0xFD, 0x02, 0x94, 0x3D, 0x52,
	0x88, 0xEF, 0x35, 0x95, 0x53, 0xDC, 0x11, 0x63, 0xE1, 0x1F, 0x7E, 0x08, 0xC0, 0x64, 0xA0, 0xCB, 0xB9, 0x10, 0x94, 0xD1, 0x7F, 0xAC, 0xDA, 0x88, 0x5D, 0xF1,
	0x8F, 0x35, 0xD6, 0xA4, 0x30, 0xC4, 0x20, 0x4F, 0xF7, 0x29, 0xEB, 0x90, 0x66, 0xAD, 0xCD, 0xB7, 0x98, 0x64, 0xEE, 0xD7, 0x12, 0x6C, 0x97, 0x1C, 0x56, 0x3D,
	0xB5, 0x9A, 0x4E, 0xAA, 0x8F, 0x74, 0x5F, 0x4B, 0x29, 0xD4, 0xEA, 0xB1, 0x08, 0xC7, 0xE3, 0xB8, 0x6A, 0x86, 0x41, 0x2D, 0xA7, 0x6C, 0x37, 0xAF, 0x8C, 0xFE,
	0x17, 0xDC, 0x5A, 0x71, 0x24, 0xE6, 0x53, 0x03, 0x46, 0xC8, 0x10, 0x20, 0x7E, 0xE6, 0x09, 0x54, 0xFA, 0x6B, 0x0B, 0xD5, 0x73, 0x5A, 0x9C, 0x48, 0xDE, 0xF2,
	0x0F, 0x20, 0x4C, 0x2E, 0x57, 0x9E, 0xB0, 0x44, 0x85, 0xBE, 0xA8, 0x74, 0x15, 0x84, 0x22, 0x40, 0xF0, 0x0E, 0xE1, 0xBC, 0x7D, 0x59, 0x6B, 0x9D, 0x2D, 0x64,
	0x1D, 0x79, 0x05, 0x9E, 0xCB, 0xEA, 0x91, 0xBC, 0xF4, 0xD0, 0x74, 0x4F, 0x3D, 0x81, 0x2F, 0xF8, 0xC0, 0x3A, 0x69, 0xF6, 0x74, 0xEA, 0xB1, 0x52, 0x9E, 0xB9,
	0x62, 0xCA, 0x7A, 0xAD, 0x91, 0x3D, 0xA6, 0xCC, 0x1C, 0x37, 0x01, 0x55, 0x79, 0x8C, 0xE6, 0xD1, 0x04, 0x76, 0xF9, 0xC8, 0x08, 0x62, 0xF6, 0xD6, 0x32, 0xEA,
	0xDC, 0x6C, 0xC7, 0x9E, 0x38, 0xAE, 0x8D, 0x11, 0x21, 0xCE, 0x7F, 0xFB, 0xA9, 0x48, 0xD4, 0xF4, 0x5C, 0x1A, 0x52, 0x04, 0x47, 0x27, 0x13, 0x96, 0xB4, 0x01,
	0xA7, 0x46, 0x8F, 0x18, 0x9B, 0xBC, 0x23, 0x91, 0x37, 0x6D, 0xDB, 0x41, 0x13, 0x98, 0x27, 0x46, 0xE9, 0xBD, 0x0F, 0xE3, 0x88, 0xC3, 0x6D, 0xA0, 0xD7, 0x3C,
	0xAA, 0x84, 0xBC, 0x36, 0x22, 0x52, 0xE2, 0x28, 0x91, 0x4A, 0xC3, 0x5E, 0x01, 0xB2, 0x2A, 0x54, 0x1A, 0xF6, 0x45, 0xC1, 0xF0, 0x4C, 0x93, 0x3C, 0x5A, 0xC0,
	0x25, 0x37, 0xB1, 0x72, 0x83, 0xD6, 0x9D, 0xE0, 0x5C, 0xEE, 0xC3, 0xE4, 0x62, 0xCD, 0x77, 0xB1, 0xD7, 0x31, 0x60, 0xA5, 0xC4, 0x06, 0xFF, 0x2C, 0x87, 0xEF,
	0xD8, 0x17, 0x83, 0x32, 0x67, 0x4D, 0x7B, 0xB2, 0xFE, 0x1A, 0xBB, 0x28, 0x67, 0x15, 0xF2, 0x93, 0x69, 0xAB, 0xD1, 0x3B, 0x6F, 0x0B, 0xA2, 0x7C, 0x96, 0xFB,
	0x86, 0x63, 0xCE, 0x70, 0xA5, 0x32, 0xDE, 0xAD, 0x0C, 0xDA, 0x15, 0xE9, 0x7D, 0x97, 0x0E, 0xC6, 0xFB, 0x39, 0x66, 0x7D, 0xAA, 0x31, 0x6E, 0x20, 0x86, 0x11,
	0xED, 0x4E, 0x08, 0x84, 0xE2, 0x14, 0x7C, 0x48, 0xCD, 0xAB, 0x73, 0xBF, 0x50, 0xB4, 0x5D, 0xFB, 0x97, 0x25, 0xEC, 0x2F, 0x5E, 0x94, 0x4F, 0xED, 0xC9, 0x47,
	0x5A, 0xDC, 0x7E, 0x13, 0x9D, 0x88, 0xE6, 0xBA, 0x25, 0xD2, 0x36, 0x47, 0xD8, 0x09, 0x93, 0xE6, 0x5D, 0x05, 0x78, 0x61, 0xBB, 0x6F, 0x89, 0xA2, 0x4E, 0x68,
	0xE1, 0x2E, 0xA5, 0x1E, 0xB7, 0x0A, 0xCA, 0x42, 0x91, 0x53, 0xD9, 0x2C, 0x9F, 0x5D, 0xFB, 0x41, 0xCF, 0x55, 0xF2, 0x8C, 0x63, 0x1F, 0x59, 0x39, 0x01, 0xA0,
	0x71, 0xC6, 0x07, 0xD6, 0xA3, 0x14, 0x42, 0xDD, 0x81, 0x70, 0x0C, 0xA0, 0xB4, 0x2C, 0xC6, 0x4C, 0xFF, 0x5C, 0x1B, 0xAE, 0xF5, 0x53, 0x13, 0x74, 0xA8, 0xBB,
	0x3D, 0x25, 0xFE, 0x9C, 0x87, 0x39, 0x20, 0xCC, 0x30, 0xF8, 0x02, 0xBB, 0x88, 0x46, 0x5A, 0x8F, 0xE8, 0x4F, 0xF7, 0x15, 0xA4, 0xF3, 0xB7, 0x3B, 0xC2, 0x23,
	0x72, 0xAC, 0x97, 0x29, 0xB4, 0x18, 0x9C, 0xEB, 0xDF, 0x92, 0xF3, 0x24, 0xDC, 0x3E, 0xAE, 0x56, 0x73, 0xCE, 0xBF, 0x05, 0xA9, 0x34, 0x8E, 0xFA, 0x1D, 0x40,
	0xE8, 0x0A, 0xBE, 0x31, 0x45, 0x7A, 0x66, 0x8E, 0xC8, 0xEB, 0x1E, 0xE1, 0x7E, 0xB3, 0xD5, 0x49, 0xC5, 0xF3, 0x55, 0xE5, 0xB3, 0x41, 0x60, 0xD1, 0xEF, 0x13,
	0xDA, 0x70, 0xC2, 0x2B, 0x87, 0x75, 0xCF, 0x0C, 0x65, 0x7C, 0xE9, 0x8D, 0xC9, 0x0F, 0x65, 0xE7, 0x3C, 0x75, 0xC3, 0x30, 0x0C, 0x7D, 0xCE, 0x65, 0x8B, 0x19,
	0x7F, 0xF5, 0x36, 0x8D, 0x62, 0xF7, 0x23, 0xE3, 0xD0, 0x60, 0x76, 0x89, 0x98, 0x64, 0x72, 0xA5, 0xCF, 0x0F, 0xDF, 0x3C, 0xA1, 0x5A, 0x2F, 0x68, 0x4F, 0x12,
	0x6C, 0x2B, 0x17, 0xA7, 0x94, 0x09, 0x76, 0x99, 0x1B, 0xA8, 0x26, 0x79, 0xB2, 0x06, 0x3E, 0x9A, 0x60, 0xDF, 0x36, 0x4C, 0x99, 0xB0, 0x04, 0x47, 0xDE, 0x33,
	0x80, 0xBE, 0x03, 0xD9, 0x51, 0xA8, 0x41, 0xB8, 0x4B, 0xA6, 0x2E, 0xE8, 0x48, 0xBA, 0x0C, 0x9E, 0x4D, 0x7C, 0xB4, 0x3F, 0x54, 0xC1, 0x02, 0xD6, 0xAF, 0x24,
	0xDA, 0x82, 0xEC, 0x95, 0x29, 0xBF, 0x04, 0x83, 0xF8, 0x99, 0xCD, 0x8F, 0xEC, 0xAF, 0xE0, 0x7D, 0x67, 0x27, 0x4C, 0xD6, 0x84, 0x3A, 0xC9, 0x4E, 0x9F, 0xFC,
	0xD0, 0x1D, 0xAF, 0xBC, 0x25, 0x84, 0xFE, 0x1B, 0x55, 0x6B, 0xA4, 0x1E, 0xF6, 0x4C, 0x93, 0x6B, 0xFC, 0x84, 0xC9, 0x6D, 0x17, 0xF9, 0x58, 0xC0, 0x95, 0x6B,
	0x28, 0xE7, 0xD3, 0x17, 0x6A, 0x9A, 0x12, 0xA6, 0xED, 0x33, 0x52, 0xF5, 0x3E, 0x06, 0x56, 0xB2, 0x4D, 0xEF, 0x70, 0xB8, 0x44, 0x0A, 0xC1, 0x37, 0x76, 0x01,
	0x53, 0x3F, 0xCA, 0xFC, 0xB8, 0xE3, 0x6D, 0xF6, 0x5B, 0x91, 0x31, 0x67, 0x80, 0x54, 0xF2, 0x02, 0x6C, 0xE3, 0xC8, 0x2D, 0xD7, 0xBB, 0xEE, 0x9A, 0x5B, 0xCF,
	0xAE, 0x2C, 0x1A, 0x5F, 0x24, 0x99, 0xDF, 0x07, 0x77, 0xD1, 0x10, 0xDE, 0x5C, 0xAF, 0x32, 0xC5, 0x8B, 0xF2, 0x2D, 0x83, 0x46, 0x6A, 0xCC, 0x17, 0x90, 0xC2,
	0x2F, 0x6A, 0x16, 0x88, 0xD8, 0x24, 0xAD, 0xE7, 0x1C, 0x5F, 0xF9, 0x9E, 0xBC, 0xD8, 0x0F, 0x89, 0xA4, 0x30, 0x0A, 0x17, 0xC1, 0xEB, 0x1F, 0xDE, 0x0F, 0x46,
	0xA3, 0x8E, 0x3C, 0x5B, 0xA8, 0x92, 0x79, 0x39, 0x85, 0x14, 0x41, 0x77, 0x0D, 0xE5, 0xA0, 0xD4, 0xF1, 0x3A, 0x89, 0xB1, 0x33, 0xA0, 0x21, 0x86, 0xEF, 0x76,
	0x01, 0x47, 0xD8, 0x5A, 0xBD, 0xDC, 0x21, 0x79, 0xA1, 0xB8, 0x5E, 0xE1, 0x9D, 0xFC, 0xCB, 0xA6, 0x35, 0x62, 0x7A, 0x52, 0xA4, 0x86, 0x45, 0x23, 0x90, 0x32,
	0x63, 0x1C, 0x58, 0x47, 0x97, 0xAD, 0x7D, 0x42, 0xB5, 0x8C, 0xC8, 0xE8, 0x77, 0xD5, 0xBF, 0x12, 0x45, 0xEB, 0x07, 0x62, 0xC8, 0x27, 0xDA, 0xB8, 0x8A, 0x37,
	0x49, 0xBA, 0x10, 0x73, 0x4F, 0xEC, 0x63, 0x42, 0xB5, 0x50, 0x39, 0xA6, 0x94, 0xFB, 0x1F, 0x72, 0x07, 0xB0, 0x95, 0xFD, 0x09, 0x85, 0x47, 0x72, 0x20, 0x7E,
	0x40, 0x58, 0x0C, 0xF6, 0x8E, 0xDC, 0x2D, 0xCF, 0x5A, 0xE1, 0x6F, 0xF3, 0xB2, 0x7F, 0xED, 0xBD, 0xD2, 0x64, 0xDA, 0x03, 0x70, 0xA5, 0x38, 0x62, 0x23, 0x30,
	0xF9, 0x9C, 0x72, 0x22, 0xB1, 0x4F, 0xF9, 0xA6, 0x6E, 0xF0, 0x63, 0xC7, 0x7B, 0x8F, 0x58, 0xAB, 0x27, 0xC2, 0xD8, 0x7D, 0xFE, 0xCB, 0x19, 0xC2, 0x60, 0x80,
	0x3D, 0xA4, 0x4E, 0xEA, 0x61, 0x3C, 0xD4, 0x2A, 0xF3, 0xC8, 0xAF, 0x00, 0xE6, 0x96, 0xD1, 0xC0, 0x19, 0x3E, 0xB4, 0x10, 0x81, 0xAC, 0x07, 0x4B, 0x98, 0xC7,
	0x3C, 0x75, 0x23, 0x8A, 0x50, 0x2B, 0xF1, 0x57, 0x14, 0xB9, 0x95, 0x09, 0x80, 0x51, 0xDC, 0xC1, 0x8C, 0xD4, 0x10, 0x94, 0x32, 0x08, 0x51, 0x20, 0xF8, 0x00,
	0xE7, 0xCC, 0x9B, 0x1B, 0x05, 0x93, 0x58, 0x0C, 0x6D, 0xDB, 0x29, 0xB9, 0xE4, 0xCF, 0x8A, 0x31, 0x15, 0xBE, 0x55, 0xA7, 0x0E, 0x38, 0x50, 0x65, 0xBB, 0x2B,
	0x6F, 0x4A, 0x9C, 0x67, 0xF0, 0xC3, 0x1D, 0x39, 0xCE, 0xE8, 0x29, 0x16, 0xDD, 0x08, 0xF9, 0x36, 0xE3, 0x98, 0xC5, 0x84, 0xD7, 0xF5, 0x4C, 0xCC, 0xA9, 0x65,
	0x16, 0xF3, 0x2D, 0x42, 0x7C, 0x58, 0xB4, 0xE1, 0x9D, 0xD3, 0xAF, 0x2F, 0x66, 0x44, 0x7F, 0xF5, 0x6A, 0x2F, 0xE2, 0x9E, 0x88, 0xF1, 0x4D, 0x0A, 0x9B, 0x13,
	0x69, 0xC5, 0x76, 0xE1, 0x8E, 0x69, 0xE5, 0x99, 0xD7, 0x8A, 0xF5, 0x11, 0x82, 0xB0, 0xE5, 0x03, 0x74, 0x94, 0xFE, 0x62, 0x8B, 0x79, 0x56, 0xA9, 0x6B, 0xA0,
	0x5D, 0xB6, 0xA8, 0x0F, 0x46, 0x21, 0x75, 0xAD, 0x6A, 0x3E, 0xE9, 0xB7, 0x37, 0x84, 0x6B, 0x9E, 0xE6, 0xC6, 0x3B, 0x18, 0x80, 0x40, 0x6F, 0x97, 0x15, 0xDD,
	0x35, 0xA6, 0x4B, 0xB1, 0xBE, 0x3E, 0x1E, 0xAC, 0x34, 0x79, 0x5C, 0xED, 0x26, 0xF5, 0xAB, 0x45, 0x22, 0x80, 0xB7, 0x18, 0x75, 0x31, 0xA8, 0x43, 0xDD, 0x22,
	0x58, 0x33, 0xD6, 0x4E, 0xB9, 0x9E, 0x0B, 0xC1, 0x41, 0xF6, 0x90, 0x48, 0xCC, 0x1B, 0x7A, 0x69, 0xFF, 0x9D, 0x34, 0x0C, 0xDF, 0x8D, 0x28, 0x06, 0x5C, 0xCF,
	0xAC, 0x02, 0x1F, 0x66, 0xFB, 0x8E, 0xBF, 0x5B, 0xEA, 0xC5, 0x87, 0xB6, 0x57, 0xD3, 0x8A, 0x10, 0x74, 0xF7, 0x62, 0xD4, 0x92, 0xC9, 0xB6, 0x3B, 0x82, 0x52,
	0x99, 0x11, 0xCF, 0xF1, 0x3F, 0x5B, 0xED, 0x20, 0xCD, 0x60, 0x90, 0xFA, 0xC6, 0x7E, 0xA9, 0x28, 0x6F, 0xD2, 0x34, 0xE5, 0xB3, 0x11, 0xD4, 0x2D, 0x83, 0xEB,
	0x3F, 0xBB, 0xCE, 0x51, 0xE7, 0x5D, 0x1D, 0xA3, 0x79, 0xFD, 0x98, 0x47, 0xED, 0xBB, 0x50, 0xD7, 0x74, 0x2B, 0xA8, 0x06, 0x22, 0x4D, 0xFC, 0x0C, 0x71, 0x1E,
	0xEE, 0xC6, 0x27, 0x53, 0x83, 0x03, 0xE8, 0x70, 0x1B, 0xA7, 0xD6, 0x02, 0xBB, 0x36, 0x60, 0x94, 0x05, 0xC4, 0xA1, 0x4B, 0xBD, 0x07, 0x77, 0x3A, 0x9F, 0x12,
	0xEA, 0x44, 0xF4, 0x17, 0x5E, 0x83, 0x21, 0x72, 0x5F, 0xBE, 0x03, 0x54, 0x8C, 0x28, 0x0A, 0x92, 0xB2, 0xC2, 0x82, 0xD2, 0x50, 0xBE, 0xDA, 0x19, 0x77, 0x2A,
	0x8B, 0xA3, 0x0B, 0x46, 0xEE, 0xDB, 0x7C, 0x39, 0xA2, 0x2C, 0xE4, 0x98, 0x61, 0x39, 0xD1, 0x9C, 0xB4, 0x42, 0x2A, 0x56, 0xFD, 0x47, 0x8F, 0x67, 0xE0, 0xF8,
	0x71, 0xB2, 0x2C, 0x7C, 0xDF, 0x68, 0x96, 0xD7, 0x17, 0x51, 0xB8, 0x65, 0x89, 0x06, 0x96, 0xAE, 0xDD, 0x4C, 0xA3, 0xF0, 0x3A, 0x9C, 0xF8, 0xAD, 0xE2, 0xD5,
	0x63, 0x78, 0x3C, 0x2B, 0xF6, 0x43, 0x11, 0x6E, 0x34, 0x90, 0x59, 0xC7, 0x39, 0xF5, 0x5E, 0xD0, 0x92, 0x69, 0xB2, 0xCD, 0x5E, 0xBE, 0x80, 0x41, 0xAA, 0x08,
	0x7A, 0xE5, 0x16, 0xC1, 0xA2, 0xDA, 0x10, 0xC3, 0x2E, 0x7B, 0x1E, 0x9E, 0x49, 0xD8, 0x54, 0xFE, 0x0D, 0x35, 0x82, 0xF4, 0xAD, 0xE6, 0x2B, 0xDC, 0xC9, 0x56,
	0xBF, 0x7A, 0x2A, 0x8F, 0xC6, 0x0D, 0xDA, 0x77, 0x23, 0x6C, 0x36, 0x9F, 0x17, 0xF0, 0xDB, 0x00, 0x8F, 0x64, 0xB1, 0x25, 0xA6, 0xF3, 0x10, 0xAD, 0xE0, 0x7B,
	0x14, 0xB7, 0x32, 0x52, 0x10, 0xF4, 0x8D, 0x03, 0xD9, 0x51, 0xB9, 0xF9, 0x8E, 0x49, 0x6B, 0xF5, 0x7C, 0x89, 0x63, 0x9C, 0xEF, 0x57, 0xC9, 0x0B, 0x87, 0x23,
	0xAA, 0x8F, 0x45, 0xB5, 0x1C, 0x58, 0x6E, 0x43, 0x93, 0x1E, 0x73, 0x36, 0xF0, 0x1C, 0x40, 0xE8, 0x68, 0x54, 0xB1, 0x89, 0x4A, 0x12, 0xC6, 0x59, 0x83, 0xAB,
	0x4D, 0x71, 0xA0, 0xEB, 0xC7, 0xE1, 0x80, 0x42, 0xD1, 0x6C, 0x4B, 0x23, 0x9B, 0xC4, 0x84, 0x1D, 0x9D, 0x43, 0x70, 0x24, 0xEF, 0x69, 0x13, 0x2E, 0xDC, 0x21,
	0x5D, 0x35, 0x0B, 0xB2, 0x3A, 0x22, 0xE2, 0xA9, 0x40, 0xB6, 0xEB, 0xCE, 0x13, 0x6B, 0xC6, 0xEC, 0x27, 0x9D, 0xCC, 0x01, 0x7F, 0xFB, 0xB1, 0x48, 0x6A, 0xA5,
	0xCD, 0x02, 0x98, 0x31, 0x19, 0xCE, 0xE8, 0x96, 0xB7, 0xFB, 0x42, 0x23, 0xCB, 0xB9, 0x14, 0x56, 0x39, 0x0B, 0x96, 0x60, 0xBA, 0x03, 0x89, 0xEA, 0x66, 0x40,
	0xFE, 0xDD, 0x77, 0xE7, 0xAD, 0x36, 0x94, 0xC9, 0xA2, 0x73, 0xC3, 0xAD, 0x99, 0xCB, 0xEA, 0x4F, 0xBC, 0x72, 0x06, 0x83, 0x69, 0x2B, 0x5B, 0x76, 0x3B, 0xE2,
	0xA2, 0x5F, 0xD5, 0x88, 0x37, 0xC1, 0xA3, 0x5E, 0xD3, 0x0A, 0xE1, 0x87, 0x59, 0xFD, 0x7D, 0xC1, 0xF2, 0x3D, 0x61, 0x7B, 0x2D, 0x05, 0xD4, 0x68, 0x94, 0x32,
	0xDF, 0x87, 0x78, 0x4B, 0xF7, 0x1D, 0xE4, 0x9E, 0x35, 0xB0, 0xD5, 0x58, 0x05, 0xA8, 0x2B, 0xBF, 0x5A, 0xD5, 0x4A, 0x1D, 0x87, 0x3B, 0x54, 0x09, 0xE1, 0x6E,
	0x1B, 0x91, 0xD7, 0x46, 0xCC, 0xFA, 0x16, 0xC2, 0x9B, 0xF2, 0x85, 0x2E, 0x08, 0x73, 0x49, 0xF9, 0x0F, 0xE9, 0x4D, 0x30, 0x17, 0x9A, 0x50, 0x33, 0xBA, 0x25,
	0xAB, 0x49, 0x6E, 0xA1, 0x0A, 0xDB, 0x51, 0x8C, 0xA3, 0xF1, 0x1A, 0x5F, 0xFC, 0x27, 0xD7, 0xC1, 0xAA, 0x2C, 0x55, 0x7D, 0xF9, 0x16, 0x28, 0x95, 0x6C, 0xCF,
	0x4C, 0x15, 0x82, 0x0D, 0xB7, 0xFC, 0x5E, 0xD1, 0xF0, 0x81, 0x44, 0x2D, 0xA5, 0x7F, 0xF2, 0x30, 0x98, 0x52, 0x8E, 0xDE, 0x47, 0x1D, 0xB3, 0x56, 0xBD, 0x96,
	0x18, 0xB0, 0x7A, 0x68, 0xB8, 0x8D, 0xEE, 0x78, 0xC6, 0x0D, 0xDB, 0x62, 0x11, 0xE2, 0x90, 0x28, 0xBD, 0xAC, 0xEC, 0x71, 0x35, 0xBD, 0x7E, 0x43, 0xAD, 0x9C,
	0x04, 0x65, 0x8D, 0x72, 0xCC, 0x3F, 0xBF, 0x4F, 0x76, 0xF0, 0xB9, 0x39, 0x8F, 0xF7, 0x67, 0x9A, 0xE5, 0x79, 0x01, 0xB2, 0x27, 0x95, 0xBA, 0xFD, 0x59, 0x04,
	0x65, 0xAC, 0x11, 0xB9, 0x37, 0x70, 0xA7, 0x00, 0xD7, 0xF6, 0x3F, 0xCB, 0xE3, 0x2B, 0x56, 0xDB, 0x22, 0xCE, 0x41, 0xAC, 0xF7, 0x93, 0x73, 0x3C, 0x85, 0xCF,
	0x1A, 0xF8, 0x5B, 0x44, 0x1F, 0xCA, 0x0D, 0x4E, 0xE5, 0x10, 0xC9, 0x52, 0xB7, 0x34, 0xF0, 0x12, 0xDC, 0x08, 0x8B, 0xA3, 0xCA, 0x0D, 0x86, 0x20, 0xA4, 0xDF,
	0xC6, 0x3F, 0x21, 0xA9, 0x33, 0xDF, 0x65, 0x14, 0x74, 0xD3, 0x3D, 0xC6, 0x24, 0xE2, 0x79, 0x5D, 0xEC, 0x26, 0xC8, 0x61, 0x8D, 0x7B, 0x21, 0x66, 0x8B, 0xA5,
	0x3A, 0x85, 0x9D, 0x04, 0x60, 0x29, 0x1C, 0x48, 0xB3, 0xF1, 0xA0, 0x53, 0x78, 0x37, 0xD5, 0x80, 0x9B, 0x64, 0xB4, 0x88, 0xD5, 0x6A, 0x90, 0x75, 0x1F, 0xE6,
	0x47, 0x9A, 0xB1, 0x6A, 0x5E, 0xE0, 0x2F, 0x45, 0xE5, 0x63, 0x53, 0x04, 0x2E, 0x72, 0x57, 0x8D, 0x48, 0x9C, 0xCA, 0x50, 0x8B, 0x0D, 0xE8, 0xB5, 0x89, 0x43,
	0x9F, 0xD4, 0x0A, 0x82, 0xFD, 0x4D, 0x34, 0xA0, 0xEA, 0x50, 0x06, 0xF1, 0xC0, 0x13, 0xFD, 0x6F, 0xE3, 0x83, 0xBF, 0x68, 0xD5, 0x04, 0x2B, 0xC4, 0xB5, 0x68,
	0x92, 0x01, 0xDF, 0xFC, 0x3C, 0x22, 0xA1, 0x2D, 0xF4, 0x3D, 0xD0, 0x85, 0x59, 0xC7, 0x25, 0x38, 0xF5, 0x1D, 0xAE, 0x97, 0x72, 0xBD, 0xD3, 0x7F, 0xB3, 0xEE,
	0xD7, 0xC2, 0xF6, 0x6D, 0x1A, 0xF0, 0xAA, 0x34, 0x9B, 0x6C, 0x51, 0xF9, 0x1C, 0xC3, 0x3C, 0xAF, 0x99, 0x13, 0xD0, 0xB0, 0x10, 0xBA, 0x72, 0xD2, 0x5D, 0x48,
	0xB2, 0xCB, 0x52, 0xA2, 0xEC, 0x31, 0x57, 0x9A, 0xE4, 0x45, 0x0F, 0xA6, 0xEB, 0x19, 0x30, 0x56, 0x77, 0xEE, 0xC3, 0x5B, 0x07, 0xDD, 0xA5, 0x69, 0x15, 0xFF,
	0x78, 0x92, 0xD2, 0x81, 0x58, 0xEC, 0x16, 0x34, 0xF4, 0x12, 0x4A, 0x9D, 0x19, 0x08, 0x7E, 0x2B, 0x40, 0xBD, 0xDA, 0x5F, 0x80, 0x18, 0xCF, 0x2E, 0x71, 0x91,
	0x55, 0x66, 0xE4, 0x77, 0x29, 0x6A, 0xDE, 0x42, 0x32, 0x98, 0x20, 0x7E, 0x2F, 0x90, 0x39, 0x0A, 0xAA, 0x8C, 0x18, 0x7F, 0x6D, 0xFF, 0x24, 0xD2, 0x4F, 0xC0,
	0xB2, 0x8D, 0xA8, 0x0B, 0x46, 0x82, 0xB5, 0x1B, 0x93, 0x2F, 0xBF, 0xAA, 0x43, 0x0E, 0xBB, 0x4C, 0x00, 0xC5, 0xA1, 0x42, 0x92, 0xAC, 0x68, 0x86, 0x3A, 0x61,
	0xB6, 0xE7, 0xA2, 0x73, 0x22, 0x4A, 0x02, 0xED, 0xA2, 0xBB, 0x07, 0xF2, 0xD9, 0x1F, 0x45, 0xC1, 0xF3, 0x57, 0x92, 0x82, 0xF8, 0xAC, 0xE0, 0x68, 0xEB, 0x15,
	0xD9, 0x75, 0x41, 0xF6, 0xCB, 0xB1, 0x36, 0x94, 0x60, 0x87, 0x7A, 0x3E, 0x6D, 0xCD, 0x1B, 0xD9, 0x97, 0x67, 0xFA, 0x4E, 0x7A, 0xE4, 0x03, 0x53, 0xE8, 0xA0,
	0x2D, 0xF1, 0x6D, 0x28, 0x7D, 0x5D, 0xC9, 0x24, 0xE6, 0xBB, 0xFE, 0xCE, 0x93, 0x0E, 0x57, 0x86, 0xF5, 0xC5, 0xB1, 0xDF, 0x3B, 0x5C, 0x84, 0xA9, 0x31, 0xB6,
	0x8C, 0x03, 0x3A, 0xA4, 0x1B, 0xCC, 0x0C, 0x53, 0xC4, 0x05, 0x9F, 0xB7, 0x5C, 0xC4,
};

/*
================================================================================

	COMMANDS

================================================================================
*/

/*
===============
TexMgr_SetFilterModes
===============
*/
static void TexMgr_SetFilterModes (gltexture_t *glt)
{
	ZEROED_STRUCT (VkDescriptorImageInfo, image_info);
	image_info.imageView = glt->image_view;
	image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
	qboolean enable_anisotropy = vid_anisotropic.value && (!(glt->flags & TEXPREF_NOPICMIP) || (glt->flags & TEXPREF_WARPIMAGE));

	VkSampler point_sampler = enable_anisotropy ? vulkan_globals.point_aniso_sampler_lod_bias : vulkan_globals.point_sampler_lod_bias;
	VkSampler linear_sampler = enable_anisotropy ? vulkan_globals.linear_aniso_sampler_lod_bias : vulkan_globals.linear_sampler_lod_bias;

	if (glt->flags & TEXPREF_NEAREST)
		image_info.sampler = point_sampler;
	else if (glt->flags & TEXPREF_LINEAR)
		image_info.sampler = linear_sampler;
	else
		image_info.sampler = (vid_filter.value == 1) ? point_sampler : linear_sampler;

	ZEROED_STRUCT (VkWriteDescriptorSet, texture_write);
	texture_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	texture_write.dstSet = glt->descriptor_set;
	texture_write.dstBinding = 0;
	texture_write.dstArrayElement = 0;
	texture_write.descriptorCount = 1;
	texture_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	texture_write.pImageInfo = &image_info;

	vkUpdateDescriptorSets (vulkan_globals.device, 1, &texture_write, 0, NULL);
}

/*
===============
TexMgr_UpdateTextureDescriptorSets
===============
*/
void TexMgr_UpdateTextureDescriptorSets (void)
{
	gltexture_t *glt;

	for (glt = active_gltextures; glt; glt = glt->next)
		TexMgr_SetFilterModes (glt);
}

/*
===============
TexMgr_Imagelist_f -- report loaded textures
===============
*/
static void TexMgr_Imagelist_f (void)
{
	float		 mb;
	float		 texels = 0;
	gltexture_t *glt;

	for (glt = active_gltextures; glt; glt = glt->next)
	{
		if (glt->flags & TEXPREF_MIPMAP)
			texels += glt->width * glt->height * 4.0f / 3.0f;
		else
			texels += (glt->width * glt->height);
		if (glt->source_format == SRC_RGBA_CUBEMAP)
		{
			Con_SafePrintf ("   %4i CUBE  %s\n", glt->width, glt->name);
			texels *= 6.0f;
		}
		else
			Con_SafePrintf ("   %4i x%4i %s\n", glt->width, glt->height, glt->name);
	}

	mb = (texels * 4) / 0x100000;
	Con_Printf ("%i textures %i pixels %1.1f megabytes\n", numgltextures, (int)texels, mb);
}

/*
================================================================================

	TEXTURE MANAGER

================================================================================
*/

/*
================
TexMgr_FindTexture
================
*/
gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt = NULL;

	if (name)
	{
		for (glt = active_gltextures; glt; glt = glt->next)
		{
			if (glt->owner == owner && !strcmp (glt->name, name))
				goto unlock_mutex;
		}
	}

unlock_mutex:
	SDL_UnlockMutex (texmgr_mutex);
	return glt;
}

/*
================
TexMgr_NewTexture
================
*/
gltexture_t *TexMgr_NewTexture (void)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt;

	glt = free_gltextures;
	free_gltextures = glt->next;
	glt->next = active_gltextures;
	active_gltextures = glt;

	numgltextures++;
	SDL_UnlockMutex (texmgr_mutex);
	return glt;
}

static void GL_DeleteTexture (gltexture_t *texture);

/*
================
TexMgr_FreeTexture
================
*/
void TexMgr_FreeTexture (gltexture_t *kill)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt;

	if (kill == NULL)
	{
		Con_Printf ("TexMgr_FreeTexture: NULL texture\n");
		goto unlock_mutex;
	}

	if (active_gltextures == kill)
	{
		active_gltextures = kill->next;
		kill->next = free_gltextures;
		free_gltextures = kill;

		GL_DeleteTexture (kill);
		numgltextures--;
		goto unlock_mutex;
	}

	for (glt = active_gltextures; glt; glt = glt->next)
	{
		if (glt->next == kill)
		{
			glt->next = kill->next;
			kill->next = free_gltextures;
			free_gltextures = kill;

			GL_DeleteTexture (kill);
			numgltextures--;
			goto unlock_mutex;
		}
	}

	Con_Printf ("TexMgr_FreeTexture: not found\n");
unlock_mutex:
	SDL_UnlockMutex (texmgr_mutex);
}

/*
================
TexMgr_FreeTextures

compares each bit in "flags" to the one in glt->flags only if that bit is active in "mask"
================
*/
void TexMgr_FreeTextures (unsigned int flags, unsigned int mask)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt, *next;

	for (glt = active_gltextures; glt; glt = next)
	{
		next = glt->next;
		if ((glt->flags & mask) == (flags & mask))
			TexMgr_FreeTexture (glt);
	}
	SDL_UnlockMutex (texmgr_mutex);
}

/*
================
TexMgr_FreeTexturesForOwner
================
*/
void TexMgr_FreeTexturesForOwner (qmodel_t *owner)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt, *next;

	for (glt = active_gltextures; glt; glt = next)
	{
		next = glt->next;
		if (glt && glt->owner == owner)
			TexMgr_FreeTexture (glt);
	}
	SDL_UnlockMutex (texmgr_mutex);
}

/*
================
TexMgr_DeleteTextureObjects
================
*/
void TexMgr_DeleteTextureObjects (void)
{
	SDL_LockMutex (texmgr_mutex);
	gltexture_t *glt;

	for (glt = active_gltextures; glt; glt = glt->next)
		GL_DeleteTexture (glt);
	SDL_UnlockMutex (texmgr_mutex);
}

/*
================================================================================

	INIT

================================================================================
*/

/*
================
TexMgr_InitHeap
================
*/
void TexMgr_InitHeap ()
{
	ZEROED_STRUCT (VkImageCreateInfo, image_create_info);
	image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
	image_create_info.imageType = VK_IMAGE_TYPE_2D;
	image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
	image_create_info.extent.width = 1;
	image_create_info.extent.height = 1;
	image_create_info.extent.depth = 1;
	image_create_info.mipLevels = 1;
	image_create_info.arrayLayers = 1;
	image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
	image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
	image_create_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
							  VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
	image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
	image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

	VkImage	 dummy_image;
	VkResult err = vkCreateImage (vulkan_globals.device, &image_create_info, NULL, &dummy_image);
	if (err != VK_SUCCESS)
		Sys_Error ("vkCreateImage failed with code %i", (int)err);

	VkMemoryRequirements memory_requirements;
	vkGetImageMemoryRequirements (vulkan_globals.device, dummy_image, &memory_requirements);
	const uint32_t memory_type_index = GL_MemoryTypeFromProperties (memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);

	const VkDeviceSize heap_memory_size = TEXTURE_HEAP_MEMORY_SIZE_MB * (VkDeviceSize)1024 * (VkDeviceSize)1024;
	texmgr_heap = GL_HeapCreate (heap_memory_size, TEXTURE_HEAP_PAGE_SIZE, memory_type_index, VULKAN_MEMORY_TYPE_DEVICE, false, "Texture Heap");

	vkDestroyImage (vulkan_globals.device, dummy_image, NULL);
}

/*
=================
TexMgr_LoadPalette -- johnfitz -- was VID_SetPalette, moved here, renamed, rewritten
=================
*/
void TexMgr_LoadPalette (void)
{
	byte *src, *dst;
	int	  i;
	FILE *f;

	COM_FOpenFile ("gfx/palette.lmp", &f, NULL);
	if (!f)
		Sys_Error ("Couldn't load gfx/palette.lmp");

	byte pal[768];
	if (fread (pal, 1, 768, f) != 768)
		Sys_Error ("Couldn't load gfx/palette.lmp");
	fclose (f);

	// standard palette, 255 is transparent
	dst = (byte *)d_8to24table;
	src = pal;
	for (i = 0; i < 256; i++)
	{
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = 255;
	}
	((byte *)&d_8to24table[255])[3] = 0;

	// fullbright palette, 0-223 are black (for additive blending)
	src = pal + 224 * 3;
	dst = (byte *)&d_8to24table_fbright[224];
	for (i = 224; i < 256; i++)
	{
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = 255;
	}
	for (i = 0; i < 224; i++)
	{
		dst = (byte *)&d_8to24table_fbright[i];
		dst[3] = 255;
		dst[2] = dst[1] = dst[0] = 0;
	}

	// nobright palette, 224-255 are black (for additive blending)
	dst = (byte *)d_8to24table_nobright;
	src = pal;
	for (i = 0; i < 256; i++)
	{
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = *src++;
		*dst++ = 255;
	}
	for (i = 224; i < 256; i++)
	{
		dst = (byte *)&d_8to24table_nobright[i];
		dst[3] = 255;
		dst[2] = dst[1] = dst[0] = 0;
	}

	// fullbright palette, for fence textures
	memcpy (d_8to24table_fbright_fence, d_8to24table_fbright, 256 * 4);
	d_8to24table_fbright_fence[255] = 0; // Alpha of zero.

	// nobright palette, for fence textures
	memcpy (d_8to24table_nobright_fence, d_8to24table_nobright, 256 * 4);
	d_8to24table_nobright_fence[255] = 0; // Alpha of zero.

	// conchars palette, 0 and 255 are transparent
	memcpy (d_8to24table_conchars, d_8to24table, 256 * 4);
	((byte *)&d_8to24table_conchars[0])[3] = 0;
}

/*
=================
TexMgr_LoadMiptexPalette -- convert a 24bit color palette to 32bit
=================
*/
static void TexMgr_LoadMiptexPalette (byte *in, byte *out, int numcolors, unsigned flags)
{
	extern cvar_t gl_fullbrights;
	int			  i, numnobright;

	if (numcolors == 0)
		return;

	if (!(flags & (TEXPREF_FULLBRIGHT | TEXPREF_NOBRIGHT)))
	{
		for (i = 0; i < numcolors; i++)
		{
			*out++ = *in++;
			*out++ = *in++;
			*out++ = *in++;
			*out++ = 255;
		}
	}
	else
	{
		numnobright = q_min (224, numcolors);
		if (flags & TEXPREF_NOBRIGHT)
		{
			// nobright palette
			if (!gl_fullbrights.value)
				numnobright = numcolors;
			for (i = 0; i < numnobright; i++)
			{
				*out++ = *in++;
				*out++ = *in++;
				*out++ = *in++;
				*out++ = 255;
			}
			// 224-255 are black (for additive blending)
			for (i = numnobright; i < numcolors; i++)
			{
				*out++ = 0;
				*out++ = 0;
				*out++ = 0;
				*out++ = 255;
			}
		}
		else
		{
			// fullbright palette
			// 0-223 are black (for additive blending)
			for (i = 0; i < numnobright; i++)
			{
				*out++ = 0;
				*out++ = 0;
				*out++ = 0;
				*out++ = 255;
			}
			in += numnobright * 3;
			for (i = numnobright; i < numcolors; i++)
			{
				*out++ = *in++;
				*out++ = *in++;
				*out++ = *in++;
				*out++ = 255;
			}
		}
	}

	// 255 is transparent
	if (flags & TEXPREF_ALPHA)
		out[-1] = 0;
}

/*
================
TexMgr_NewGame
================
*/
void TexMgr_NewGame (void)
{
	TexMgr_FreeTextures (0, TEXPREF_PERSIST); // deletes all textures where TEXPREF_PERSIST is unset
	TexMgr_LoadPalette ();
}

/*
================
TexMgr_Init

must be called before any texture loading
================
*/
void TexMgr_Init (void)
{
	int				  i;
	static byte		  notexture_data[16] = {159, 91, 83, 255, 0, 0, 0, 255, 0, 0, 0, 255, 159, 91, 83, 255};					// black and pink checker
	static byte		  nulltexture_data[16] = {127, 191, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 127, 191, 255, 255};				// black and blue checker
	static byte		  whitetexture_data[16] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; // white
	static byte		  greytexture_data[16] = {127, 127, 127, 255, 127, 127, 127, 255, 127, 127, 127, 255, 127, 127, 127, 255};	// 50% grey
	extern texture_t *r_notexture_mip, *r_notexture_mip2;

	texmgr_mutex = SDL_CreateMutex ();

	// init texture list
	free_gltextures = (gltexture_t *)Mem_Alloc (MAX_GLTEXTURES * sizeof (gltexture_t));
	active_gltextures = NULL;
	for (i = 0; i < MAX_GLTEXTURES - 1; i++)
		free_gltextures[i].next = &free_gltextures[i + 1];
	free_gltextures[i].next = NULL;
	numgltextures = 0;

	// palette
	TexMgr_LoadPalette ();

	Cvar_RegisterVariable (&gl_max_size);
	Cvar_RegisterVariable (&gl_picmip);
	Cmd_AddCommand ("imagelist", &TexMgr_Imagelist_f);

	// load notexture images
	notexture = TexMgr_LoadImage (
		NULL, "notexture", 2, 2, SRC_RGBA, notexture_data, "", (src_offset_t)notexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
	nulltexture = TexMgr_LoadImage (
		NULL, "nulltexture", 2, 2, SRC_RGBA, nulltexture_data, "", (src_offset_t)nulltexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
	whitetexture = TexMgr_LoadImage (
		NULL, "whitetexture", 2, 2, SRC_RGBA, whitetexture_data, "", (src_offset_t)whitetexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
	greytexture = TexMgr_LoadImage (
		NULL, "greytexture", 2, 2, SRC_RGBA, greytexture_data, "", (src_offset_t)greytexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
	greylightmap = TexMgr_LoadImage (
		NULL, "greytexture", 2, 2, SRC_LIGHTMAP, greytexture_data, "", (src_offset_t)greytexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);

	TEMP_ALLOC (byte, bluenoise_rgba, sizeof (bluenoise_data) * 4);
	for (i = 0; i < sizeof (bluenoise_data); ++i)
		for (int j = 0; j < 3; ++j)
			bluenoise_rgba[i * 4 + j] = bluenoise_data[i];
	bluenoisetexture = TexMgr_LoadImage (
		NULL, "bluenoise", 64, 64, SRC_RGBA, bluenoise_rgba, "", (src_offset_t)greytexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
	TEMP_FREE (bluenoise_rgba);

	// have to assign these here becuase Mod_Init is called before TexMgr_Init
	r_notexture_mip->gltexture = r_notexture_mip2->gltexture = notexture;
}

/*
================================================================================

	IMAGE LOADING

================================================================================
*/

/*
================
TexMgr_Downsample
================
*/
static unsigned *TexMgr_Downsample (unsigned *data, int in_width, int in_height, int out_width, int out_height)
{
	const int out_size_bytes = out_width * out_height * 4;

	assert ((out_width >= 1) && (out_width <= in_width));
	assert ((out_height >= 1) && (out_height <= in_height));

	TEMP_ALLOC (byte, image_resize_buffer, out_size_bytes);
	stbir_resize_uint8 ((byte *)data, in_width, in_height, 0, image_resize_buffer, out_width, out_height, 0, 4);
	memcpy (data, image_resize_buffer, out_size_bytes);
	TEMP_FREE (image_resize_buffer);

	return data;
}

/*
===============
TexMgr_AlphaEdgeFix

eliminate pink edges on sprites, etc.
operates in place on 32bit data
===============
*/
static void TexMgr_AlphaEdgeFix (byte *data, int width, int height)
{
	int	  i, j, n = 0, b, c[3] = {0, 0, 0}, lastrow, thisrow, nextrow, lastpix, thispix, nextpix;
	byte *dest = data;

	for (i = 0; i < height; i++)
	{
		lastrow = width * 4 * ((i == 0) ? height - 1 : i - 1);
		thisrow = width * 4 * i;
		nextrow = width * 4 * ((i == height - 1) ? 0 : i + 1);

		for (j = 0; j < width; j++, dest += 4)
		{
			if (dest[3]) // not transparent
				continue;

			lastpix = 4 * ((j == 0) ? width - 1 : j - 1);
			thispix = 4 * j;
			nextpix = 4 * ((j == width - 1) ? 0 : j + 1);

			b = lastrow + lastpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = thisrow + lastpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = nextrow + lastpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = lastrow + thispix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = nextrow + thispix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = lastrow + nextpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = thisrow + nextpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}
			b = nextrow + nextpix;
			if (data[b + 3])
			{
				c[0] += data[b];
				c[1] += data[b + 1];
				c[2] += data[b + 2];
				n++;
			}

			// average all non-transparent neighbors
			if (n)
			{
				dest[0] = (byte)(c[0] / n);
				dest[1] = (byte)(c[1] / n);
				dest[2] = (byte)(c[2] / n);

				n = c[0] = c[1] = c[2] = 0;
			}
		}
	}
}

/*
================
TexMgr_8to32
================
*/
static void TexMgr_8to32 (byte *in, unsigned *out, int pixels, unsigned int *usepal)
{
	for (int i = 0; i < pixels; i++)
		*out++ = usepal[*in++];
}

/*
================
TexMgr_DeriveNumMips
================
*/
static int TexMgr_DeriveNumMips (int width, int height)
{
	int num_mips = 0;
	while (width >= 1 && height >= 1)
	{
		width /= 2;
		height /= 2;
		num_mips += 1;
	}
	return num_mips;
}

/*
================
TexMgr_DeriveStagingSize
================
*/
static int TexMgr_DeriveStagingSize (int width, int height)
{
	int size = 0;
	while (width >= 1 && height >= 1)
	{
		size += width * height * 4;
		width /= 2;
		height /= 2;
	}
	return size;
}

/*
================
TexMgr_PreMultiply32
================
*/
static void TexMgr_PreMultiply32 (byte *in, size_t width, size_t height)
{
	size_t pixels = width * height;
	while (pixels-- > 0)
	{
		in[0] = ((int)in[0] * (int)in[3]) >> 8;
		in[1] = ((int)in[1] * (int)in[3]) >> 8;
		in[2] = ((int)in[2] * (int)in[3]) >> 8;
		in += 4;
	}
}

/*
================
TexMgr_LoadImage32 -- handles 32bit source data
================
*/
static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data)
{
	GL_DeleteTexture (glt);

	// do this before any rescaling
	if (glt->flags & TEXPREF_PREMULTIPLY)
		TexMgr_PreMultiply32 ((byte *)data, glt->width, glt->height);

	// mipmap down
	int picmip = (glt->flags & TEXPREF_NOPICMIP) ? 0 : q_max ((int)gl_picmip.value, 0);
	int mipwidth = q_max (glt->width >> picmip, 1);
	int mipheight = q_max (glt->height >> picmip, 1);

	const qboolean is_cube = glt->source_format == SRC_RGBA_CUBEMAP;
	int maxsize = (int)(is_cube ? vulkan_globals.device_properties.limits.maxImageDimensionCube : vulkan_globals.device_properties.limits.maxImageDimension2D);
	if (!(glt->flags & TEXPREF_NOPICMIP) && gl_max_size.value)
		maxsize = q_min (q_max ((int)gl_max_size.value, 1), maxsize);
	if ((mipwidth > maxsize) || (mipheight > maxsize))
	{
		if (mipwidth >= mipheight)
		{
			mipheight = q_max ((mipheight * maxsize) / mipwidth, 1);
			mipwidth = maxsize;
		}
		else
		{
			mipwidth = q_max ((mipwidth * maxsize) / mipheight, 1);
			mipheight = maxsize;
		}
	}
	// has alpha detection :
	if (data && glt->source_format == SRC_RGBA && !(glt->flags & TEXPREF_ALPHAPIXELS))
	{
		int	  num_pixels = glt->width * glt->height;
		byte *pixel_data = (byte *)data;

		for (int i = 0; i < num_pixels; i++)
		{
			if (pixel_data[i * 4 + 3] != 255)
			{
				glt->flags |= TEXPREF_ALPHA;
				glt->flags |= TEXPREF_ALPHAPIXELS;
				break;
			}
		}
	}
	// downsizing according to gl_picmip / gl_max_size: (debug only)
	// don't attempt to downsize below 1x1
	if (((int)glt->width != mipwidth || (int)glt->height != mipheight) && (mipwidth >= 1) && (mipheight >= 1))
	{
		if (is_cube)
			for (int i = 0; i < 6; i++)
				TexMgr_Downsample ((unsigned int *)((byte **)data)[i], glt->width, glt->height, mipwidth, mipheight);
		else
			TexMgr_Downsample (data, glt->width, glt->height, mipwidth, mipheight);
		glt->width = mipwidth;
		glt->height = mipheight;
		if (glt->flags & TEXPREF_ALPHA)
			TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height);
	}
	int num_mips = (glt->flags & TEXPREF_MIPMAP) ? TexMgr_DeriveNumMips (glt->width, glt->height) : 1;

	SDL_LockMutex (texmgr_mutex);
	const qboolean warp_image = (glt->flags & TEXPREF_WARPIMAGE);
	if (warp_image)
		num_mips = WARPIMAGEMIPS;

	// Check for sanity. This should never be reached.
	if (num_mips > MAX_MIPS)
		Sys_Error ("Texture has over %d mips", MAX_MIPS);

	const qboolean lightmap = glt->source_format == SRC_LIGHTMAP;
	const qboolean surface_indices = glt->source_format == SRC_SURF_INDICES;
	const qboolean ten_bit = lightmap && vulkan_globals.color_format == VK_FORMAT_A2B10G10R10_UNORM_PACK32;

	VkResult err;

	const VkFormat format = surface_indices ? VK_FORMAT_R32_UINT : ten_bit ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_R8G8B8A8_UNORM;

	ZEROED_STRUCT (VkImageCreateInfo, image_create_info);
	image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
	image_create_info.imageType = VK_IMAGE_TYPE_2D;
	image_create_info.format = format;
	image_create_info.extent.width = glt->width;
	image_create_info.extent.height = glt->height;
	image_create_info.extent.depth = 1;
	image_create_info.mipLevels = num_mips;
	image_create_info.arrayLayers = is_cube ? 6 : 1;
	image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
	image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
	if (warp_image)
		image_create_info.usage =
			(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
			 VK_IMAGE_USAGE_STORAGE_BIT);
	else if (lightmap)
		image_create_info.usage = (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT);
	else
		image_create_info.usage = (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);

	image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
	image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
	if (is_cube)
		image_create_info.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;

	err = vkCreateImage (vulkan_globals.device, &image_create_info, NULL, &glt->image);
	if (err != VK_SUCCESS)
		Sys_Error ("vkCreateImage failed with code %i", (int)err);
	GL_SetObjectName ((uint64_t)glt->image, VK_OBJECT_TYPE_IMAGE, va ("%s image", glt->name));

	VkMemoryRequirements memory_requirements;
	vkGetImageMemoryRequirements (vulkan_globals.device, glt->image, &memory_requirements);

	glt->allocation = GL_HeapAllocate (texmgr_heap, memory_requirements.size, memory_requirements.alignment, &num_vulkan_tex_allocations);
	err = vkBindImageMemory (vulkan_globals.device, glt->image, GL_HeapGetAllocationMemory (glt->allocation), GL_HeapGetAllocationOffset (glt->allocation));
	if (err != VK_SUCCESS)
		Sys_Error ("vkBindImageMemory failed with code %i", (int)err);

	ZEROED_STRUCT (VkImageViewCreateInfo, image_view_create_info);
	image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	image_view_create_info.image = glt->image;
	image_view_create_info.viewType = is_cube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
	image_view_create_info.format = format;
	image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_R;
	image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_G;
	image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_B;
	image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_A;
	image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	image_view_create_info.subresourceRange.baseMipLevel = 0;
	image_view_create_info.subresourceRange.levelCount = num_mips;
	image_view_create_info.subresourceRange.baseArrayLayer = 0;
	image_view_create_info.subresourceRange.layerCount = is_cube ? 6 : 1;

	err = vkCreateImageView (vulkan_globals.device, &image_view_create_info, NULL, &glt->image_view);
	if (err != VK_SUCCESS)
		Sys_Error ("vkCreateImageView failed with code %i", (int)err);
	GL_SetObjectName ((uint64_t)glt->image_view, VK_OBJECT_TYPE_IMAGE_VIEW, va ("%s image view", glt->name));

	// Allocate and update descriptor for this texture
	glt->descriptor_set = R_AllocateDescriptorSet (&vulkan_globals.single_texture_set_layout);
	GL_SetObjectName ((uint64_t)glt->descriptor_set, VK_OBJECT_TYPE_DESCRIPTOR_SET, va ("%s desc set", glt->name));

	TexMgr_SetFilterModes (glt);

	if (warp_image || lightmap)
	{
		image_view_create_info.subresourceRange.levelCount = 1;
		err = vkCreateImageView (vulkan_globals.device, &image_view_create_info, NULL, &glt->target_image_view);
		if (err != VK_SUCCESS)
			Sys_Error ("vkCreateImageView failed with code %i", (int)err);
		GL_SetObjectName ((uint64_t)glt->target_image_view, VK_OBJECT_TYPE_IMAGE_VIEW, va ("%s target image view", glt->name));
	}
	else
	{
		glt->target_image_view = VK_NULL_HANDLE;
	}

	// Don't upload data for warp image, will be updated by rendering
	if (warp_image)
	{
		ZEROED_STRUCT (VkFramebufferCreateInfo, framebuffer_create_info);
		framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
		framebuffer_create_info.renderPass = vulkan_globals.warp_render_pass;
		framebuffer_create_info.attachmentCount = 1;
		framebuffer_create_info.pAttachments = &glt->target_image_view;
		framebuffer_create_info.width = glt->width;
		framebuffer_create_info.height = glt->height;
		framebuffer_create_info.layers = 1;
		err = vkCreateFramebuffer (vulkan_globals.device, &framebuffer_create_info, NULL, &glt->frame_buffer);
		if (err != VK_SUCCESS)
			Sys_Error ("vkCreateFramebuffer failed with code %i", (int)err);
		GL_SetObjectName ((uint64_t)glt->frame_buffer, VK_OBJECT_TYPE_FRAMEBUFFER, va ("%s framebuffer", glt->name));
	}

	if (warp_image)
	{
		// Allocate and update descriptor for this texture
		glt->storage_descriptor_set = R_AllocateDescriptorSet (&vulkan_globals.single_texture_cs_write_set_layout);
		GL_SetObjectName ((uint64_t)glt->storage_descriptor_set, VK_OBJECT_TYPE_DESCRIPTOR_SET, va ("%s storage desc set", glt->name));

		ZEROED_STRUCT (VkDescriptorImageInfo, output_image_info);
		output_image_info.imageView = glt->target_image_view;
		output_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;

		ZEROED_STRUCT (VkWriteDescriptorSet, storage_image_write);
		storage_image_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		storage_image_write.dstBinding = 0;
		storage_image_write.dstArrayElement = 0;
		storage_image_write.descriptorCount = 1;
		storage_image_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
		storage_image_write.dstSet = glt->storage_descriptor_set;
		storage_image_write.pImageInfo = &output_image_info;

		vkUpdateDescriptorSets (vulkan_globals.device, 1, &storage_image_write, 0, NULL);
	}
	else
	{
		glt->storage_descriptor_set = VK_NULL_HANDLE;
	}

	SDL_UnlockMutex (texmgr_mutex);

	// Don't upload data for warp image, will be updated by rendering
	if (warp_image)
		return;

	glt->frame_buffer = VK_NULL_HANDLE;

	// Upload
	ZEROED_STRUCT_ARRAY (VkBufferImageCopy, regions, MAX_MIPS);

	int staging_size = (glt->flags & TEXPREF_MIPMAP) ? TexMgr_DeriveStagingSize (mipwidth, mipheight) : (mipwidth * mipheight * 4);
	if (is_cube)
		staging_size *= 6;

	VkBuffer		staging_buffer;
	VkCommandBuffer command_buffer;
	int				staging_offset;
	unsigned char  *staging_memory = R_StagingAllocate (staging_size, 4, &command_buffer, &staging_buffer, &staging_offset);

	int num_regions = 0;

	if (glt->flags & TEXPREF_MIPMAP)
	{
		int mip_offset = 0;
		mipwidth = glt->width;
		mipheight = glt->height;

		while (mipwidth >= 1 && mipheight >= 1)
		{
			regions[num_regions].bufferOffset = staging_offset + mip_offset;
			regions[num_regions].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			regions[num_regions].imageSubresource.layerCount = 1;
			regions[num_regions].imageSubresource.mipLevel = num_regions;
			regions[num_regions].imageExtent.width = mipwidth;
			regions[num_regions].imageExtent.height = mipheight;
			regions[num_regions].imageExtent.depth = 1;

			mip_offset += mipwidth * mipheight * 4;
			num_regions += 1;

			mipwidth /= 2;
			mipheight /= 2;
		}
	}
	else if (is_cube)
	{
		for (int i = 0; i < 6; i++)
		{
			regions[i].bufferOffset = staging_offset + (i * mipwidth * mipheight * 4);
			regions[i].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			regions[i].imageSubresource.layerCount = 1;
			regions[i].imageSubresource.baseArrayLayer = i;
			regions[i].imageSubresource.mipLevel = 0;
			regions[i].imageExtent.width = mipwidth;
			regions[i].imageExtent.height = mipheight;
			regions[i].imageExtent.depth = 1;
		}
	}
	else
	{
		regions[0].bufferOffset = staging_offset;
		regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
		regions[0].imageSubresource.layerCount = 1;
		regions[0].imageSubresource.mipLevel = 0;
		regions[0].imageExtent.width = mipwidth;
		regions[0].imageExtent.height = mipheight;
		regions[0].imageExtent.depth = 1;
	}

	ZEROED_STRUCT (VkImageMemoryBarrier, image_memory_barrier);
	image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
	image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
	image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
	image_memory_barrier.image = glt->image;
	image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	image_memory_barrier.subresourceRange.baseMipLevel = 0;
	image_memory_barrier.subresourceRange.levelCount = num_mips;
	image_memory_barrier.subresourceRange.baseArrayLayer = 0;
	image_memory_barrier.subresourceRange.layerCount = is_cube ? 6 : 1;

	image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
	image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
	image_memory_barrier.srcAccessMask = 0;
	image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
	vkCmdPipelineBarrier (command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier);

	vkCmdCopyBufferToImage (command_buffer, staging_buffer, glt->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, num_mips * (is_cube ? 6 : 1), regions);

	image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
	image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
	image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
	image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
	vkCmdPipelineBarrier (command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier);

	R_StagingBeginCopy ();
	if (glt->flags & TEXPREF_MIPMAP)
	{
		int mip_offset = 0;
		mipwidth = glt->width;
		mipheight = glt->height;

		while (mipwidth >= 1 && mipheight >= 1)
		{
			memcpy (staging_memory + mip_offset, data, mipwidth * mipheight * 4);

			mip_offset += mipwidth * mipheight * 4;
			num_regions += 1;

			if (mipwidth > 1 && mipheight > 1)
				TexMgr_Downsample (data, mipwidth, mipheight, mipwidth / 2, mipheight / 2);

			mipwidth /= 2;
			mipheight /= 2;
		}
	}
	else if (is_cube)
	{
		const int reorder[] = {3, 1, 4, 5, 0, 2};
		staging_size /= 6;
		for (int i = 0; i < 6; i++)
			memcpy (staging_memory + i * staging_size, ((byte **)data)[reorder[i]], staging_size);
	}
	else
	{
		if (!ten_bit)
			memcpy (staging_memory, data, staging_size);
		else
			for (byte *p = (byte *)data; p < (byte *)data + staging_size; p += 4, staging_memory += 4)
				*(unsigned *)staging_memory = p[0] | p[1] << 10 | p[2] << 20;
	}
	R_StagingEndCopy ();
}

/*
================
TexMgr_LoadImage8 -- handles 8bit source data, then passes it to LoadImage32
================
*/
static void TexMgr_LoadImage8 (gltexture_t *glt, byte *data, unsigned int *usepal)
{
	GL_DeleteTexture (glt);

	extern cvar_t gl_fullbrights;
	int			  i;

	// HACK HACK HACK -- taken from tomazquake
	if (strstr (glt->name, "shot1sid") && glt->width == 32 && glt->height == 32 && CRC_Block (data, 1024) == 65393)
	{
		// This texture in b_shell1.bsp has some of the first 32 pixels painted white.
		// They are invisible in software, but look really ugly in GL. So we just copy
		// 32 pixels from the bottom to make it look nice.
		memcpy (data, data + 32 * 31, 32);
	}

	// detect false alpha cases
	if (glt->flags & TEXPREF_ALPHA && !(glt->flags & TEXPREF_CONCHARS))
	{
		for (i = 0; i < (int)(glt->width * glt->height); i++)
			if (data[i] == 255) // transparent index
				break;
		if (i == (int)(glt->width * glt->height))
			glt->flags -= TEXPREF_ALPHA;
	}

	// choose palette and padbyte
	if (!usepal)
	{
		if (glt->flags & TEXPREF_FULLBRIGHT)
		{
			if (glt->flags & TEXPREF_ALPHA)
				usepal = d_8to24table_fbright_fence;
			else
				usepal = d_8to24table_fbright;
		}
		else if (glt->flags & TEXPREF_NOBRIGHT && gl_fullbrights.value)
		{
			if (glt->flags & TEXPREF_ALPHA)
				usepal = d_8to24table_nobright_fence;
			else
				usepal = d_8to24table_nobright;
		}
		else if (glt->flags & TEXPREF_CONCHARS)
		{
			usepal = d_8to24table_conchars;
		}
		else
		{
			usepal = d_8to24table;
		}
	}

	// convert to 32bit
	TEMP_ALLOC (unsigned, converted, glt->width * glt->height);
	TexMgr_8to32 (data, converted, glt->width * glt->height, usepal);

	// fix edges
	if (glt->flags & TEXPREF_ALPHA)
		TexMgr_AlphaEdgeFix ((byte *)converted, glt->width, glt->height);

	// upload it
	TexMgr_LoadImage32 (glt, (unsigned *)converted);

	TEMP_FREE (converted);
}

/*
================
TexMgr_LoadLightmap -- handles lightmap data
================
*/
static void TexMgr_LoadLightmap (gltexture_t *glt, byte *data)
{
	TexMgr_LoadImage32 (glt, (unsigned *)data);
}

/*
================
TexMgr_LoadImage8Valve
================
*/
static void TexMgr_LoadImage8Valve (gltexture_t *glt, byte *data)
{
	byte		  *in;
	unsigned short colors;

	in = data + glt->source_width * glt->source_height / 64 * 85;

	memcpy (&colors, in, 2);
	colors = LittleShort (colors);

	TEMP_ALLOC (byte, usepal, colors * 4);
	TexMgr_LoadMiptexPalette (in + 2, usepal, colors, glt->flags);
	TexMgr_LoadImage8 (glt, data, (unsigned *)usepal);
	TEMP_FREE (usepal);
}

/*
================
TexMgr_LoadImage -- the one entry point for loading all textures
================
*/
gltexture_t *TexMgr_LoadImage (
	qmodel_t *owner, const char *name, int width, int height, enum srcformat format, byte *data, const char *source_file, src_offset_t source_offset,
	unsigned flags)
{
	unsigned short crc = 0;
	gltexture_t	  *glt;

	if (isDedicated)
		return NULL;

	// cache check
	if (flags & TEXPREF_OVERWRITE)
		switch (format)
		{
		case SRC_INDEXED:
		case SRC_INDEXED_PALETTE:
			crc = CRC_Block (data, width * height);
			break;
		case SRC_LIGHTMAP:
			crc = CRC_Block (data, width * height * LIGHTMAP_BYTES);
			break;
		case SRC_RGBA:
			crc = CRC_Block (data, width * height * 4);
			break;
		default: /* not reachable but avoids compiler warnings */
			crc = 0;
		}
	if ((flags & TEXPREF_OVERWRITE) && (glt = TexMgr_FindTexture (owner, name)))
	{
		if (glt->source_crc == crc)
			return glt;
	}
	else
		glt = TexMgr_NewTexture ();

	// copy data
	glt->owner = owner;
	q_strlcpy (glt->name, name, sizeof (glt->name));
	glt->path_id = (glt->owner ? glt->owner->path_id : 0);
	glt->width = width;
	glt->height = height;
	glt->flags = flags;
	glt->shirt = -1;
	glt->pants = -1;
	q_strlcpy (glt->source_file, source_file, sizeof (glt->source_file));
	glt->source_offset = source_offset;
	glt->source_format = format;
	glt->source_width = width;
	glt->source_height = height;
	glt->source_crc = crc;

	// upload it
	switch (glt->source_format)
	{
	case SRC_INDEXED:
		TexMgr_LoadImage8 (glt, data, NULL);
		break;
	case SRC_LIGHTMAP:
		TexMgr_LoadLightmap (glt, data);
		break;
	case SRC_RGBA:
	case SRC_SURF_INDICES:
	case SRC_RGBA_CUBEMAP:
		TexMgr_LoadImage32 (glt, (unsigned *)data);
		break;
	case SRC_INDEXED_PALETTE:
		TexMgr_LoadImage8Valve (glt, data);
		break;
	}

	return glt;
}

/*
================================================================================

	COLORMAPPING AND TEXTURE RELOADING

================================================================================
*/

/*
================
TexMgr_ReloadImage -- reloads a texture, and colormaps it if needed
================
*/
void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants)
{
	byte  translation[256];
	byte *src, *dst, *data = NULL, *allocated = NULL, *translated = NULL;
	int	  size, i;
	//
	// get source data
	//

	if (glt->source_file[0] && glt->source_offset)
	{
		// lump inside file
		FILE *f;
		COM_FOpenFile (glt->source_file, &f, NULL);
		if (!f)
			goto invalid;
		Sys_fseek (f, glt->source_offset, SEEK_CUR);
		size = glt->source_width * glt->source_height;
		/* should be SRC_INDEXED, but no harm being paranoid:  */
		if (glt->source_format == SRC_RGBA)
		{
			size *= 4;
		}
		else if (glt->source_format == SRC_LIGHTMAP)
		{
			size *= LIGHTMAP_BYTES;
		}
		allocated = data = (byte *)Mem_Alloc (size);
		if (fread (data, 1, size, f) != size)
			goto invalid;
		fclose (f);
	}
	else if (glt->source_file[0] && !glt->source_offset)
	{
		allocated = data =
			Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height, &glt->source_format, glt->path_id); // simple file
	}
	else if (!glt->source_file[0] && glt->source_offset)
	{
		data = (byte *)glt->source_offset; // image in memory
	}
	if (!data)
	{
	invalid:
		Con_Printf ("TexMgr_ReloadImage: invalid source for %s\n", glt->name);
		return;
	}

	glt->width = glt->source_width;
	glt->height = glt->source_height;
	//
	// apply shirt and pants colors
	//
	// if shirt and pants are -1,-1, use existing shirt and pants colors
	// if existing shirt and pants colors are -1,-1, don't bother colormapping
	if (shirt > -1 && pants > -1)
	{
		if (glt->source_format == SRC_INDEXED)
		{
			glt->shirt = shirt;
			glt->pants = pants;
		}
		else
			Con_Printf ("TexMgr_ReloadImage: can't colormap a non SRC_INDEXED texture: %s\n", glt->name);
	}
	if (glt->shirt > -1 && glt->pants > -1)
	{
		// create new translation table
		for (i = 0; i < 256; i++)
			translation[i] = i;

		shirt = glt->shirt * 16;
		if (shirt < 128)
		{
			for (i = 0; i < 16; i++)
				translation[TOP_RANGE + i] = shirt + i;
		}
		else
		{
			for (i = 0; i < 16; i++)
				translation[TOP_RANGE + i] = shirt + 15 - i;
		}

		pants = glt->pants * 16;
		if (pants < 128)
		{
			for (i = 0; i < 16; i++)
				translation[BOTTOM_RANGE + i] = pants + i;
		}
		else
		{
			for (i = 0; i < 16; i++)
				translation[BOTTOM_RANGE + i] = pants + 15 - i;
		}

		// translate texture
		size = glt->width * glt->height;
		dst = translated = (byte *)Mem_Alloc (size);
		src = data;

		for (i = 0; i < size; i++)
			*dst++ = translation[*src++];

		data = translated;
	}
	//
	// upload it
	//
	switch (glt->source_format)
	{
	case SRC_INDEXED:
		TexMgr_LoadImage8 (glt, data, NULL);
		break;
	case SRC_LIGHTMAP:
		TexMgr_LoadLightmap (glt, data);
		break;
	case SRC_RGBA:
	case SRC_SURF_INDICES:
	case SRC_RGBA_CUBEMAP:
		TexMgr_LoadImage32 (glt, (unsigned *)data);
		break;
	case SRC_INDEXED_PALETTE:
		TexMgr_LoadImage8Valve (glt, data);
		break;
	}

	Mem_Free (translated);
	Mem_Free (allocated);
}

/*
================
TexMgr_ReloadNobrightImages -- reloads all texture that were loaded with the nobright palette.  called when gl_fullbrights changes
================
*/
void TexMgr_ReloadNobrightImages (void)
{
	gltexture_t *glt;

	for (glt = active_gltextures; glt; glt = glt->next)
		if (glt->flags & TEXPREF_NOBRIGHT)
			TexMgr_ReloadImage (glt, -1, -1);
}

/*
================================================================================

	TEXTURE BINDING / TEXTURE UNIT SWITCHING

================================================================================
*/

typedef struct
{
	VkImage				image;
	VkImageView			target_image_view;
	VkImageView			image_view;
	VkFramebuffer		frame_buffer;
	VkDescriptorSet		descriptor_set;
	VkDescriptorSet		storage_descriptor_set;
	glheapallocation_t *allocation;
} texture_garbage_t;

static int				 current_garbage_index;
static int				 num_garbage_textures[2];
static texture_garbage_t texture_garbage[MAX_GLTEXTURES][2];

/*
================
TexMgr_CollectGarbage
================
*/
void TexMgr_CollectGarbage (void)
{
	SDL_LockMutex (texmgr_mutex);

	int				   num;
	int				   i;
	texture_garbage_t *garbage;

	current_garbage_index = (current_garbage_index + 1) % 2;
	num = num_garbage_textures[current_garbage_index];
	for (i = 0; i < num; ++i)
	{
		garbage = &texture_garbage[i][current_garbage_index];
		if (garbage->frame_buffer != VK_NULL_HANDLE)
			vkDestroyFramebuffer (vulkan_globals.device, garbage->frame_buffer, NULL);
		if (garbage->target_image_view)
			vkDestroyImageView (vulkan_globals.device, garbage->target_image_view, NULL);
		vkDestroyImageView (vulkan_globals.device, garbage->image_view, NULL);
		vkDestroyImage (vulkan_globals.device, garbage->image, NULL);
		R_FreeDescriptorSet (garbage->descriptor_set, &vulkan_globals.single_texture_set_layout);
		if (garbage->storage_descriptor_set)
			R_FreeDescriptorSet (garbage->descriptor_set, &vulkan_globals.single_texture_cs_write_set_layout);

		GL_HeapFree (texmgr_heap, garbage->allocation, &num_vulkan_tex_allocations);
	}
	num_garbage_textures[current_garbage_index] = 0;

	SDL_UnlockMutex (texmgr_mutex);
}

/*
================
GL_DeleteTexture
================
*/
static void GL_DeleteTexture (gltexture_t *texture)
{
	SDL_LockMutex (texmgr_mutex);

	int				   garbage_index;
	texture_garbage_t *garbage;

	if (texture->image_view == VK_NULL_HANDLE)
		goto mutex_unlock;

	if (in_update_screen)
	{
		garbage_index = num_garbage_textures[current_garbage_index]++;
		garbage = &texture_garbage[garbage_index][current_garbage_index];
		garbage->image = texture->image;
		garbage->target_image_view = texture->target_image_view;
		garbage->image_view = texture->image_view;
		garbage->frame_buffer = texture->frame_buffer;
		garbage->descriptor_set = texture->descriptor_set;
		garbage->storage_descriptor_set = texture->storage_descriptor_set;
		garbage->allocation = texture->allocation;
	}
	else
	{
		GL_WaitForDeviceIdle ();

		if (texture->frame_buffer != VK_NULL_HANDLE)
			vkDestroyFramebuffer (vulkan_globals.device, texture->frame_buffer, NULL);
		if (texture->target_image_view)
			vkDestroyImageView (vulkan_globals.device, texture->target_image_view, NULL);
		vkDestroyImageView (vulkan_globals.device, texture->image_view, NULL);
		vkDestroyImage (vulkan_globals.device, texture->image, NULL);
		R_FreeDescriptorSet (texture->descriptor_set, &vulkan_globals.single_texture_set_layout);
		if (texture->storage_descriptor_set)
			R_FreeDescriptorSet (texture->storage_descriptor_set, &vulkan_globals.single_texture_cs_write_set_layout);

		GL_HeapFree (texmgr_heap, texture->allocation, &num_vulkan_tex_allocations);
	}

	texture->frame_buffer = VK_NULL_HANDLE;
	texture->target_image_view = VK_NULL_HANDLE;
	texture->image_view = VK_NULL_HANDLE;
	texture->image = VK_NULL_HANDLE;
	texture->allocation = NULL;

mutex_unlock:
	SDL_UnlockMutex (texmgr_mutex);
}

glheapstats_t *TexMgr_GetHeapStats (void)
{
	return GL_HeapGetStats (texmgr_heap);
}
