#include "quakedef.h"
#ifndef SERVERONLY
#include "shader.h"
#include "gl_draw.h"

#define WIN32_BLOATED
#include "glquake.h"

qboolean r2d_canhwgamma;	//says the video code has successfully activated hardware gamma
texid_t missing_texture;
texid_t missing_texture_gloss;
texid_t missing_texture_normal;

texid_t translate_texture;
shader_t *translate_shader;

texid_t ch_int_texture;
vec3_t ch_color;
shader_t *shader_crosshair;

static mpic_t *conback;
static mpic_t *draw_backtile;
shader_t *shader_draw_fill, *shader_draw_fill_trans;
mpic_t		*draw_disc;

shader_t *shader_contrastup;
shader_t *shader_contrastdown;
shader_t *shader_brightness;
shader_t *shader_gammacb;
shader_t *shader_polyblend;
shader_t *shader_menutint;

#define DRAW_QUADS 128
static int		draw_active_flags;
static shader_t *draw_active_shader;
static avec4_t	draw_active_colour;
static mesh_t	draw_mesh;
static vecV_t	draw_mesh_xyz[DRAW_QUADS];
vec2_t			draw_mesh_st[DRAW_QUADS];
static avec4_t	draw_mesh_colors[DRAW_QUADS];
index_t			r_quad_indexes[DRAW_QUADS*6];
unsigned int	r2d_be_flags;

struct
{
	lmalloc_t allocation;
	qboolean dirty;
	int lastid;
	unsigned int *data;
	shader_t *shader;
	texid_t tex;
	apic_t *pics;
} atlas;

extern cvar_t scr_conalpha;
extern cvar_t gl_conback;
extern cvar_t gl_font;
extern cvar_t gl_screenangle;
extern cvar_t vid_conautoscale;
extern cvar_t vid_conheight;
extern cvar_t vid_conwidth;
extern cvar_t con_textsize;
static void QDECL R2D_Font_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_Conautoscale_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_ScreenAngle_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_Conheight_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_Conwidth_Callback(struct cvar_s *var, char *oldvalue);

extern cvar_t crosshair;
extern cvar_t crosshaircolor;
extern cvar_t crosshairsize;
extern cvar_t crosshairimage;
extern cvar_t crosshairalpha;
static void QDECL R2D_Crosshair_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_CrosshairImage_Callback(struct cvar_s *var, char *oldvalue);
static void QDECL R2D_CrosshairColor_Callback(struct cvar_s *var, char *oldvalue);

void (*R2D_Flush)(void);

//We need this for minor things though, so we'll just use the slow accurate method.
//this is unlikly to be called too often.
qbyte GetPaletteIndex(int red, int green, int blue)
{
	//slow, horrible method.
	{
		int i, best=15;
		int bestdif=256*256*256, curdif;
		extern qbyte *host_basepal;
		qbyte *pa;

	#define _abs(x) ((x)*(x))

		pa = host_basepal;
		for (i = 0; i < 256; i++, pa+=3)
		{
			curdif = _abs(red - pa[0]) + _abs(green - pa[1]) + _abs(blue - pa[2]);
			if (curdif < bestdif)
			{
				if (curdif<1)
					return i;
				bestdif = curdif;
				best = i;
			}
		}
		return best;
	}
}
qbyte GetPaletteIndexNoFB(int red, int green, int blue)
{
	//slow, horrible method.
	{
		int i, best=15;
		int bestdif=256*256*256, curdif;
		extern qbyte *host_basepal;
		qbyte *pa;

	#define _abs(x) ((x)*(x))

		pa = host_basepal;
		for (i = 0; i < 256 - vid.fullbright; i++, pa+=3)
		{
			curdif = _abs(red - pa[0]) + _abs(green - pa[1]) + _abs(blue - pa[2]);
			if (curdif < bestdif)
			{
				if (curdif<1)
					return i;
				bestdif = curdif;
				best = i;
			}
		}
		return best;
	}
}

void R2D_Shutdown(void)
{
	Cvar_Unhook(&gl_font);
	Cvar_Unhook(&vid_conautoscale);
	Cvar_Unhook(&gl_screenangle);
	Cvar_Unhook(&vid_conheight);
	Cvar_Unhook(&vid_conwidth);

	Cvar_Unhook(&crosshair);
	Cvar_Unhook(&crosshairimage);
	Cvar_Unhook(&crosshaircolor);

	BZ_Free(cl_stris);
	cl_stris = NULL;
	BZ_Free(cl_strisvertv);
	cl_strisvertv = NULL;
	BZ_Free(cl_strisvertc);
	cl_strisvertc = NULL;
	BZ_Free(cl_strisvertt);
	cl_strisvertt = NULL;
	BZ_Free(cl_strisidx);
	cl_strisidx = NULL;
	cl_numstrisidx = 0;
	cl_maxstrisidx = 0;
	cl_numstrisvert = 0;
	cl_maxstrisvert = 0;
	cl_numstris = 0;
	cl_maxstris = 0;

	Con_FlushBackgrounds();

	if (font_console == font_default)
		font_console = NULL;

	if (font_console)
		Font_Free(font_console);
	font_console = NULL; 
	if (font_default)
		Font_Free(font_default);
	font_default = NULL; 
	if (font_tiny)
		Font_Free(font_tiny);
	font_tiny = NULL; 

#ifndef NOBUILTINMENUS
	M_ReloadMenus();
#endif

#if defined(MENU_DAT) || defined(CSQC_DAT)
	PR_ReloadFonts(false);
#endif

	while(atlas.pics)
	{
		apic_t *a = atlas.pics;
		atlas.pics = a->next;
		Z_Free(a);
	}

	Z_Free(atlas.data);
	memset(&atlas, 0, sizeof(atlas));
}

/*
Iniitalise the 2d rendering functions (including font).
Image loading code must be ready for use at this point.
*/
void R2D_Init(void)
{
	unsigned int nonorm[4*4];
	unsigned int nogloss[4*4];
	int i, j;
	unsigned int glossval;
	unsigned int normval;
	extern cvar_t gl_specular_fallback, gl_specular_fallbackexp, gl_texturemode;
	conback = NULL;

	Cvar_ForceCallback(&gl_texturemode);

	draw_mesh.istrifan = true;
	draw_mesh.numvertexes = 4;
	draw_mesh.numindexes = 6;
	draw_mesh.xyz_array = draw_mesh_xyz;
	draw_mesh.st_array = draw_mesh_st;
	draw_mesh.colors4f_array[0] = draw_mesh_colors;
	draw_mesh.indexes = r_quad_indexes;

	for (i = 0, j = 0; i < countof(r_quad_indexes); i += 6, j += 4)
	{
		r_quad_indexes[i+0] = j+0;
		r_quad_indexes[i+1] = j+1;
		r_quad_indexes[i+2] = j+2;
		r_quad_indexes[i+3] = j+2;
		r_quad_indexes[i+4] = j+3;
		r_quad_indexes[i+5] = j+0;
	}


	glossval = min(gl_specular_fallback.value*255, 255);
	glossval *= 0x10101;
	glossval |= 0x01000000 * bound(0, (int)(gl_specular_fallbackexp.value*255), 255);
	glossval = LittleLong(glossval);
	normval = 0xffff8080;
	normval = LittleLong(normval);
	for (i = 0; i < 4*4; i++)
	{
		nogloss[i] = glossval;
		nonorm[i] = normval;
	}
	missing_texture = R_LoadTexture8("no_texture", 16, 16, (unsigned char*)(r_notexture_mip+1), IF_NOALPHA|IF_NOGAMMA|IF_NOPURGE, 0);
	missing_texture_gloss = R_LoadTexture("no_texture_gloss", 4, 4, TF_RGBA32, (unsigned char*)nogloss, IF_NOGAMMA|IF_NOPURGE);
	missing_texture_normal = R_LoadTexture("no_texture_normal", 4, 4, TF_RGBA32, (unsigned char*)nonorm, IF_NOGAMMA|IF_NOPURGE);
	translate_texture = r_nulltex;
	ch_int_texture = r_nulltex;

	Shader_Init();
	BE_Init();
	Font_Init();

	draw_backtile = R_RegisterShader("gfx/backtile.lmp", SUF_NONE,
		"{\n"
			"if $nofixed\n"
				"program default2d\n"
			"endif\n"
			"affine\n"
			"nomipmaps\n"
			"{\n"
				"map $diffuse\n"
			"}\n"
		"}\n");
	TEXDOWAIT(draw_backtile->defaulttextures->base);
	if (!TEXLOADED(draw_backtile->defaulttextures->base))
		draw_backtile->defaulttextures->base = R_LoadHiResTexture("gfx/backtile", NULL, IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_NOWORKER);
	if (!TEXLOADED(draw_backtile->defaulttextures->base))
		draw_backtile->defaulttextures->base = R_LoadHiResTexture("gfx/menu/backtile", NULL, IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_NOWORKER);
	if (!TEXLOADED(draw_backtile->defaulttextures->base))
		draw_backtile->defaulttextures->base = R_LoadHiResTexture("pics/backtile", NULL, IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_NOWORKER);

	shader_draw_fill = R_RegisterShader("fill_opaque", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"map $whiteimage\n"
				"rgbgen exactvertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n");
	shader_draw_fill_trans = R_RegisterShader("fill_trans", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"map $whiteimage\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
				"blendfunc blend\n"
			"}\n"
		"}\n");
	shader_contrastup = R_RegisterShader("contrastupshader", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"nodepthtest\n"
				"map $whiteimage\n"
				"blendfunc gl_dst_color gl_one\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n"
	);
	shader_contrastdown = R_RegisterShader("contrastdownshader", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"nodepthtest\n"
				"map $whiteimage\n"
				"blendfunc gl_dst_color gl_zero\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n"
	);
	shader_brightness = R_RegisterShader("brightnessshader", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"nodepthtest\n"
				"map $whiteimage\n"
				"blendfunc gl_one gl_one\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n"
	);
	shader_gammacb = R_RegisterShader("gammacbshader", SUF_NONE,
		"{\n"
			"program defaultgammacb\n"
			"affine\n"
			"{\n"
				"map $currentrender\n"
				"nodepthtest\n"
			"}\n"
		"}\n"
	);
	shader_polyblend = R_RegisterShader("polyblendshader", SUF_NONE,
		"{\n"
			"program defaultfill\n"
			"{\n"
				"map $whiteimage\n"
				"blendfunc gl_src_alpha gl_one_minus_src_alpha\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n"
	);
	shader_menutint = R_RegisterShader("menutint", SUF_NONE,
		"{\n"
			"affine\n"
#ifdef FTE_TARGET_WEB
				//currentrender is problematic here, so avoid using it.
				"program default2d\n"
				"{\n"
					"map $whiteimage\n"
					"blendfunc gl_dst_color gl_zero\n"
					"rgbgen srgb $r_menutint\n"
				"}\n"
#else
			"if gl_menutint_shader != 0\n"
				"program menutint\n"
			"endif\n"
			"if $haveprogram\n"
				"{\n"
					"map $currentrender\n"
				"}\n"
			"else\n"
				"{\n"
					"map $whiteimage\n"
					"blendfunc gl_dst_color gl_zero\n"
					"rgbgen srgb $r_menutint\n"
				"}\n"
			"endif\n"
#endif
		"}\n"
	);
	shader_crosshair = R_RegisterShader("crosshairshader", SUF_NONE,
		"{\n"
			"if $nofixed\n"
				"program default2d\n"
			"endif\n"
			"affine\n"
			"nomipmaps\n"
			"{\n"
				"map $diffuse\n"
				"blendfunc blend\n"
				"rgbgen vertex\n"
				"alphagen vertex\n"
			"}\n"
		"}\n"
		);


	Cvar_Hook(&gl_font, R2D_Font_Callback);
	Cvar_Hook(&vid_conautoscale, R2D_Conautoscale_Callback);
	Cvar_Hook(&gl_screenangle, R2D_ScreenAngle_Callback);
	Cvar_Hook(&vid_conheight, R2D_Conheight_Callback);
	Cvar_Hook(&vid_conwidth, R2D_Conwidth_Callback);

	Cvar_Hook(&crosshair, R2D_Crosshair_Callback);
	Cvar_Hook(&crosshairimage, R2D_CrosshairImage_Callback);
	Cvar_Hook(&crosshaircolor, R2D_CrosshairColor_Callback);

	Cvar_ForceCallback(&gl_conback);
	Cvar_ForceCallback(&vid_conautoscale);
	Cvar_ForceCallback(&gl_font);

	Cvar_ForceCallback(&crosshair);
	Cvar_ForceCallback(&crosshaircolor);

#ifdef PLUGINS
	Plug_DrawReloadImages();
#endif

	R2D_Font_Changed();

	R_NetgraphInit();

	atlas.lastid = -1;
	atlas.shader = NULL;
	atlas.data = NULL;
	atlas.dirty = false;
	Mod_LightmapAllocInit(&atlas.allocation, false, min(512, sh_config.texture2d_maxsize), min(512, sh_config.texture2d_maxsize), 0);
}

mpic_t	*R2D_SafeCachePic (const char *path)
{
	shader_t *s;
	if (!qrenderer)
		return NULL;
	s = R_RegisterPic(path, NULL);
	return s;
}


mpic_t *R2D_SafePicFromWad (const char *name)
{
	void Shader_Default2D(const char *shortname, shader_t *s, const void *genargs);
	shader_t *s;
	if (!qrenderer)
		return NULL;
	s = R_RegisterCustom (va("gfx/%s", name), SUF_2D, Shader_Default2D, "wad");
	return s;
}

apic_t *R2D_LoadAtlasedPic(const char *name)
{
	apic_t *apic = Z_Malloc(sizeof(*apic));
	qpic_t *qp;
	int x,y;
	qbyte *indata = NULL;
	int atlasid;

	if (!gl_load24bit.ival)
	{
		size_t lumpsize;
		qbyte lumptype;
		qp = W_GetLumpName(name, &lumpsize, &lumptype);
		if (qp && lumptype == TYP_QPIC && lumpsize == 8+qp->width*qp->height)
		{
			apic->width = qp->width;
			apic->height = qp->height;
			indata = qp->data;
		}
	}
	
	if (!indata || apic->width > atlas.allocation.width || apic->height > atlas.allocation.height)
		atlasid = -1;	//can happen on voodoo cards
	else
		Mod_LightmapAllocBlock(&atlas.allocation, apic->width+2, apic->height+2, &apic->x, &apic->y, &atlasid);

	if (atlasid >= 0)
	{
		unsigned int *out;
		apic->x += 1;
		apic->y += 1;

		//FIXME: extend the atlas height instead, and keep going with a single atlas?
		if (atlasid != atlas.lastid)
		{
			atlas.lastid = atlasid;
			if (atlas.dirty)
				Image_Upload(atlas.tex, TF_BGRA32, atlas.data, NULL, atlas.allocation.width, atlas.allocation.height, IF_NOMIPMAP);
			atlas.tex = r_nulltex;
			atlas.shader = NULL;
			atlas.dirty = false;
			if (atlas.data)	//clear atlas data instead of reallocating it.
				memset(atlas.data, 0, sizeof(*atlas.data) * atlas.allocation.width * atlas.allocation.height);
		}

		if (!atlas.tex)
			atlas.tex = Image_CreateTexture(va("fte_atlas%i", atlasid), NULL, IF_NOMIPMAP);
		if (!atlas.shader)
		{
			atlas.shader = R_RegisterShader(va("fte_atlas%i", atlasid), SUF_NONE,
				"{\n"
					"affine\n"
					"program default2d\n"
					"{\n"
						"map $diffuse\n"
						"rgbgen vertex\n"
						"alphagen vertex\n"
						"blendfunc gl_one gl_one_minus_src_alpha\n"
					"}\n"
				"}\n"
			);
			atlas.shader->defaulttextures->base = atlas.tex;
		}

		if (!atlas.data)
			atlas.data = Z_Malloc(sizeof(*atlas.data) * atlas.allocation.width * atlas.allocation.height);

		out = atlas.data;
		out += apic->x;
		out += apic->y * atlas.allocation.width;
		apic->atlas = atlas.shader;

		//pad above. extra casts because 64bit msvc is RETARDED.
		out[-1 - (qintptr_t)atlas.allocation.width] = (indata[0] == 255)?0:d_8to24bgrtable[indata[0]];	//pad left
		for (x = 0; x < apic->width; x++)
			out[x-(qintptr_t)atlas.allocation.width] = (indata[x] == 255)?0:d_8to24bgrtable[indata[x]];
		out[x - (qintptr_t)atlas.allocation.width] = (indata[x-1] == 255)?0:d_8to24bgrtable[indata[x-1]];	//pad right
		for (y = 0; y < apic->height; y++)
		{
			out[-1] = (indata[0] == 255)?0:d_8to24bgrtable[indata[0]];	//pad left
			for (x = 0; x < apic->width; x++)
				out[x] = (indata[x] == 255)?0:d_8to24bgrtable[indata[x]];
			out[x] = (indata[x-1] == 255)?0:d_8to24bgrtable[indata[x-1]];	//pad right
			indata += x;
			out += atlas.allocation.width;
		}
		//pad below
		out[-1] = (indata[0] == 255)?0:d_8to24bgrtable[indata[0]];	//pad left
		for (x = 0; x < apic->width; x++)
			out[x] = (indata[x] == 255)?0:d_8to24bgrtable[indata[x]];
		out[x] = (indata[x-1] == 255)?0:d_8to24bgrtable[indata[x-1]];	//pad right

		//pinch inwards, for linear sampling
		apic->sl = (apic->x+0.5)/(float)atlas.allocation.width;
		apic->sh = (apic->width-1.0)/(float)atlas.allocation.width;

		apic->tl = (apic->y+0.5)/(float)atlas.allocation.height;
		apic->th = (apic->height-1.0)/(float)atlas.allocation.height;

		atlas.dirty = true;

		apic->next = atlas.pics;
		atlas.pics = apic;
	}
	else if (1)
	{
		apic->atlas = R_RegisterPic(va("gfx/%s", name), "wad");
		apic->sl = 0;
		apic->sh = 1;
		apic->tl = 0;
		apic->th = 1;

		apic->next = atlas.pics;
		atlas.pics = apic;
	}
	else
	{
		Z_Free(apic);
		return NULL;
	}
	return apic;
}

void R2D_ImageColours(float r, float g, float b, float a)
{
	draw_active_colour[0] = r;
	draw_active_colour[1] = g;
	draw_active_colour[2] = b;
	draw_active_colour[3] = a;

	Font_InvalidateColour(draw_active_colour);
}
void R2D_ImagePaletteColour(unsigned int i, float a)
{
	draw_active_colour[0] = SRGBf(host_basepal[i*3+0]/255.0);
	draw_active_colour[1] = SRGBf(host_basepal[i*3+1]/255.0);
	draw_active_colour[2] = SRGBf(host_basepal[i*3+2]/255.0);
	draw_active_colour[3] = a;

	Font_InvalidateColour(draw_active_colour);
}

//awkward and weird to use
void R2D_ImageAtlas(float x, float y, float w, float h, float s1, float t1, float s2, float t2, apic_t *pic)
{
	float newsl, newsh, newtl, newth;
	if (!pic)
		return;
	if (atlas.dirty)
	{
		Image_Upload(atlas.tex, TF_BGRA32, atlas.data, NULL, atlas.allocation.width, atlas.allocation.height, IF_NOMIPMAP);
		atlas.dirty = false;
	}

	newsl = pic->sl + s1 * pic->sh;
	newsh = newsl + (s2-s1) * pic->sh;

	newtl = pic->tl + t1 * pic->th;
	newth = newtl + (t2-t1) * pic->th;

	R2D_Image(x, y, w, h, newsl, newtl, newsh, newth, pic->atlas);
}

void R2D_ImageFlush(void)
{
	BE_DrawMesh_Single(draw_active_shader, &draw_mesh, NULL, draw_active_flags);

	R2D_Flush = NULL;
	draw_active_shader = NULL;
}
//awkward and weird to use
void R2D_Image(float x, float y, float w, float h, float s1, float t1, float s2, float t2, mpic_t *pic)
{
	int i;
	if (!pic)
		return;

	//don't draw pics if they have an image which is still loading.
	for (i = 0; i < pic->numpasses; i++)
	{
		if (pic->passes[i].texgen == T_GEN_SINGLEMAP && pic->passes[i].anim_frames[0] && pic->passes[i].anim_frames[0]->status == TEX_LOADING)
			return;
		if (pic->passes[i].texgen == T_GEN_DIFFUSE && pic->defaulttextures->base && pic->defaulttextures->base->status == TEX_LOADING)
			return;
	}

	if (draw_active_shader != pic || draw_active_flags != r2d_be_flags || draw_mesh.numvertexes+4 > DRAW_QUADS)
	{
		if (R2D_Flush)
			R2D_Flush();

		draw_active_shader = pic;
		draw_active_flags = r2d_be_flags;
		R2D_Flush = R2D_ImageFlush;

		draw_mesh.numindexes = 0;
		draw_mesh.numvertexes = 0;
	}

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+0],	x, y);
	Vector2Set(draw_mesh_st[draw_mesh.numvertexes+0],	s1, t1);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+0]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+1],	x+w, y);
	Vector2Set(draw_mesh_st[draw_mesh.numvertexes+1],	s2, t1);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+1]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+2],	x+w, y+h);
	Vector2Set(draw_mesh_st[draw_mesh.numvertexes+2],	s2, t2);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+2]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+3],	x, y+h);
	Vector2Set(draw_mesh_st[draw_mesh.numvertexes+3],	s1, t2);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+3]);

	draw_mesh.numvertexes += 4;
	draw_mesh.numindexes += 6;
}
void R2D_Image2dQuad(vec2_t points[], vec2_t texcoords[], mpic_t *pic)
{
	int i;
	if (!pic)
		return;

	//don't draw pics if they have an image which is still loading.
	for (i = 0; i < pic->numpasses; i++)
	{
		if (pic->passes[i].texgen == T_GEN_SINGLEMAP && pic->passes[i].anim_frames[0] && pic->passes[i].anim_frames[0]->status == TEX_LOADING)
			return;
		if (pic->passes[i].texgen == T_GEN_DIFFUSE && pic->defaulttextures->base && pic->defaulttextures->base->status == TEX_LOADING)
			return;
	}

	if (draw_active_shader != pic || draw_active_flags != r2d_be_flags || draw_mesh.numvertexes+4 > DRAW_QUADS)
	{
		if (R2D_Flush)
			R2D_Flush();

		draw_active_shader = pic;
		draw_active_flags = r2d_be_flags;
		R2D_Flush = R2D_ImageFlush;

		draw_mesh.numindexes = 0;
		draw_mesh.numvertexes = 0;
	}

	for (i = 0; i < 4; i++)
	{
		Vector2Copy(points[i], draw_mesh_xyz[draw_mesh.numvertexes+i]);
		Vector2Copy(texcoords[i], draw_mesh_st[draw_mesh.numvertexes+i]);
		Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+i]);
	}

	draw_mesh.numvertexes += 4;
	draw_mesh.numindexes += 6;
}

/*draws a block of the current colour on the screen*/
void R2D_FillBlock(float x, float y, float w, float h)
{
	mpic_t *pic;
	if (draw_active_colour[3] != 1)
		pic = shader_draw_fill_trans;
	else
		pic = shader_draw_fill;
	if (draw_active_shader != pic || draw_active_flags != r2d_be_flags || draw_mesh.numvertexes+4 > DRAW_QUADS)
	{
		if (R2D_Flush)
			R2D_Flush();

		draw_active_shader = pic;
		draw_active_flags = r2d_be_flags;
		R2D_Flush = R2D_ImageFlush;

		draw_mesh.numindexes = 0;
		draw_mesh.numvertexes = 0;
	}

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+0], x, y);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+0]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+1], x+w, y);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+1]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+2], x+w, y+h);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+2]);

	Vector2Set(draw_mesh_xyz[draw_mesh.numvertexes+3], x, y+h);
	Vector4Copy(draw_active_colour, draw_mesh_colors[draw_mesh.numvertexes+3]);

	draw_mesh.numvertexes += 4;
	draw_mesh.numindexes += 6;
}

void R2D_Line(float x1, float y1, float x2, float y2, shader_t *shader)
{
	if (R2D_Flush)
	{
		R2D_Flush();
		R2D_Flush = NULL;
	}

	VectorSet(draw_mesh_xyz[0], x1, y1, 0);
	Vector2Set(draw_mesh_st[0], 0, 0);
	Vector4Copy(draw_active_colour, draw_mesh_colors[0]);

	VectorSet(draw_mesh_xyz[1], x2, y2, 0);
	Vector2Set(draw_mesh_st[1], 1, 0);
	Vector4Copy(draw_active_colour, draw_mesh_colors[1]);

	if (!shader)
	{
		if (draw_active_colour[3] != 1)
			shader = shader_draw_fill_trans;
		else
			shader = shader_draw_fill;
	}

	draw_mesh.numvertexes = 2;
	draw_mesh.numindexes = 2;
	BE_DrawMesh_Single(shader, &draw_mesh, NULL, BEF_LINES);
}

void R2D_ScalePic (float x, float y, float width, float height, mpic_t *pic)
{
	R2D_Image(x, y, width, height, 0, 0, 1, 1, pic);
}

void R2D_SubPic(float x, float y, float width, float height, mpic_t *pic, float srcx, float srcy, float srcwidth, float srcheight)
{
	float newsl, newtl, newsh, newth;

	newsl = (srcx)/(float)srcwidth;
	newsh = newsl + (width)/(float)srcwidth;

	newtl = (srcy)/(float)srcheight;
	newth = newtl + (height)/(float)srcheight;

	R2D_Image(x, y, width, height, newsl, newtl, newsh, newth, pic);
}

void R2D_Letterbox(float sx, float sy, float sw, float sh, mpic_t *pic, float pw, float ph)
{
	float ratiox = (float)pw / sw;
	float ratioy = (float)ph / sh;

	if (pw<=0 || ph<=0)
	{	//no size info...
		R2D_ImageColours(0, 0, 0, 1);
		R2D_FillBlock(sx, sy, sw, sh);
		R2D_ScalePic(sx, sy, 0, 0, pic);	//in case its a videoshader with audio
	}
	else if (ratiox > ratioy)
	{	//x ratio is greatest
		float h = (sw * ph) / pw;
		float p = sh - h;

		//letterbox
		R2D_ImageColours(0, 0, 0, 1);
		R2D_FillBlock(sx, sy, sw, p/2);
		R2D_FillBlock(sx, sy + h + (p/2), sw, p/2);

		R2D_ImageColours(1, 1, 1, 1);
		R2D_ScalePic(sx, sy + p/2, sw, h, pic);
	}
	else
	{	//y ratio is greatest
		float w = (sh * pw) / ph;
		float p = sw - w;

		//sidethingies
		R2D_ImageColours(0, 0, 0, 1);
		R2D_FillBlock(sx, sy, (p/2), sh);
		R2D_FillBlock(sx + w + (p/2), sy, p/2, sh);

		R2D_ImageColours(1, 1, 1, 1);
		R2D_ScalePic(sx + p/2, sy, w, sh, pic);
	}
}

/* this is an ugly special case drawing func that's only used for the player color selection menu */
void R2D_TransPicTranslate (float x, float y, int width, int height, qbyte *pic, unsigned int *palette)
{
	int				v, u;
	unsigned		trans[64*64], *dest;
	qbyte			*src;

	dest = trans;
	for (v=0 ; v<64 ; v++, dest += 64)
	{
		src = &pic[ ((v*height)>>6) *width];
		for (u=0 ; u<64 ; u++)
			dest[u] = palette[src[(u*width)>>6]];
	}

	if (!TEXVALID(translate_texture))
	{
		translate_texture = Image_CreateTexture("***translatedpic***", NULL, IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
		translate_shader = R_RegisterShader("translatedpic", SUF_2D,
			"{\n"
				"if $nofixed\n"
					"program default2d\n"
				"endif\n"
				"nomipmaps\n"
				"{\n"
					"map $diffuse\n"
					"blendfunc blend\n"
					"rgbgen vertex\n"
					"alphagen vertex\n"
				"}\n"
			"}\n");
		translate_shader->defaulttextures->base = translate_texture;
	}
	/* could avoid reuploading already translated textures but this func really isn't used enough anyway */
	Image_Upload(translate_texture, TF_RGBA32, trans, NULL, 64, 64, IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
	R2D_ScalePic(x, y, width, height, translate_shader);
}

/*
================
Draw_ConsoleBackground

================
*/
void R2D_ConsoleBackground (int firstline, int lastline, qboolean forceopaque)
{
	float a;
	int w, h;

	w = vid.width;
	h = vid.height;

	if (forceopaque)
	{
		a = 1; // console background is necessary
	}
	else
	{
		if (!scr_conalpha.value)
			return;

		a = scr_conalpha.value;
	}

	if (R_GetShaderSizes(conback, NULL, NULL, false) <= 0)
	{
		R2D_ImageColours(0, 0, 0, a);
		R2D_FillBlock(0, lastline-(int)vid.height, w, h);
		R2D_ImageColours(1, 1, 1, 1);
	}
	else if (a >= 1)
	{
		R2D_ImageColours(1, 1, 1, 1);
		R2D_ScalePic(0, lastline-(int)vid.height, w, h, conback);
	}
	else
	{
		R2D_ImageColours(1, 1, 1, a);
		R2D_ScalePic (0, lastline - (int)vid.height, w, h, conback);
		R2D_ImageColours(1, 1, 1, 1);
	}
}

void R2D_EditorBackground (void)
{
	R2D_ImageColours(0, 0, 0, 1);
	R2D_FillBlock(0, 0, vid.width, vid.height);
//	R2D_ScalePic(0, 0, vid.width, vid.height, conback);
}

/*
=============
Draw_TileClear

This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/
void R2D_TileClear (float x, float y, float w, float h)
{
	float newsl, newsh, newtl, newth;
	newsl = (x)/(float)64;
	newsh = newsl + (w)/(float)64;

	newtl = (y)/(float)64;
	newth = newtl + (h)/(float)64;

	R2D_ImageColours(1,1,1,1);

	R2D_Image(x, y, w, h, newsl, newtl, newsh, newth, draw_backtile);
}

void QDECL R2D_Conback_Callback(struct cvar_s *var, char *oldvalue)
{
	if (qrenderer == QR_NONE || !strcmp(var->string, "none"))
	{
		conback = NULL;
		return;
	}

	if (*var->string)
		conback = R_RegisterPic(var->string, NULL);
	if (!R_GetShaderSizes(conback, NULL, NULL, true))
	{
		conback = R_RegisterCustom("console", SUF_2D, NULL, NULL);	//quake3
		if (!R_GetShaderSizes(conback, NULL, NULL, true))
		{
#ifdef HEXEN2
			if (M_GameType() == MGT_HEXEN2)
				conback = R_RegisterPic("gfx/menu/conback.lmp", NULL);
			else
#endif
				if (M_GameType() == MGT_QUAKE2)
				conback = R_RegisterPic("pics/conback.pcx", NULL);
			else
				conback = R_RegisterPic("gfx/conback.lmp", NULL);
		}
	}
}

#if defined(_WIN32) && !defined(FTE_SDL) && !defined(WINRT) && !defined(_XBOX)
#include <windows.h>
qboolean R2D_Font_WasAdded(char *buffer, char *fontfilename)
{
	char *match;
	if (!fontfilename)
		return true;
	match = strstr(buffer, fontfilename);
	if (!match)
		return false;
	if (!(match == buffer || match[-1] == ','))
		return false;
	match += strlen(fontfilename);
	if (*match && *match != ',')
		return false;
	return true;
}
extern qboolean	WinNT;
void R2D_Font_AddFontLink(char *buffer, int buffersize, char *fontname)
{
	char link[1024];
	char *res, *comma, *othercomma, *nl;
	if (fontname)
	if (MyRegGetStringValueMultiSz(HKEY_LOCAL_MACHINE, WinNT?"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink":"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\FontLink\\SystemLink", fontname, link, sizeof(link)))
	{
		res = nl = link;
		while (*nl)
		{
			nl += strlen(nl);
			nl++;
			comma = strchr(res, ',');
			if (comma)
			{
				*comma++ = 0;
				othercomma = strchr(comma, ',');
				if (othercomma)
					*othercomma = 0;
			}
			else
				comma = "";
			if (!R2D_Font_WasAdded(buffer, res))
			{
				Q_strncatz(buffer, ",", buffersize);
				Q_strncatz(buffer, res, buffersize);
				R2D_Font_AddFontLink(buffer, buffersize, comma);
			}
			res = nl;
		}
	}
}
#endif
void R2D_Font_Changed(void)
{
	float tsize;
	if (!con_textsize.modified)
		return;
	con_textsize.modified = false;

	if (con_textsize.value < 0)
		tsize = (-con_textsize.value * vid.height) / vid.pixelheight;
	else
		tsize = con_textsize.value;
	if (!tsize)
		tsize = 8;

	if (font_console == font_default)
		font_console = NULL;
	if (font_console)
		Font_Free(font_console);
	font_console = NULL;
	if (font_default)
		Font_Free(font_default);
	font_default = NULL;

	if (font_tiny)
		Font_Free(font_tiny);
	font_tiny = NULL; 

#if defined(MENU_DAT) || defined(CSQC_DAT)
	PR_ReloadFonts(true);
#endif

	if (qrenderer == QR_NONE)
		return;

#if defined(_WIN32) && !defined(FTE_SDL) && !defined(WINRT) && !defined(_XBOX)
	if (!strcmp(gl_font.string, "?"))
	{
		BOOL (APIENTRY *pChooseFontW)(LPCHOOSEFONTW) = NULL;
		dllfunction_t funcs[] =
		{
			{(void*)&pChooseFontW, "ChooseFontW"},
			{NULL}
		};
		LOGFONTW lf = {0};
		CHOOSEFONTW cf = {sizeof(cf)};
		extern HWND	mainwindow;
		font_default = Font_LoadFont(8, "");
		if (tsize != 8)
			font_console = Font_LoadFont(tsize, "");
		if (!font_console)
			font_console = font_default;

		cf.hwndOwner = mainwindow;
		cf.iPointSize = (8 * vid.rotpixelheight)/vid.height;
		cf.Flags = CF_FORCEFONTEXIST | CF_TTONLY;
		cf.lpLogFont = &lf;

		Sys_LoadLibrary("comdlg32.dll", funcs);
					
		if (pChooseFontW && pChooseFontW(&cf))
		{
			char fname[MAX_OSPATH*8];
			char lfFaceName[MAX_OSPATH];
			char *keyname;
			narrowen(lfFaceName, sizeof(lfFaceName), lf.lfFaceName);
			*fname = 0;
			//FIXME: should enumerate and split & and ignore sizes and () crap.
			if (!*fname)
			{
				keyname = va("%s%s%s (TrueType)", lfFaceName, lf.lfWeight>=FW_BOLD?" Bold":"", lf.lfItalic?" Italic":"");
				if (!MyRegGetStringValue(HKEY_LOCAL_MACHINE, WinNT?"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts":"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts", keyname, fname, sizeof(fname)))
					*fname = 0;
			}
			if (!*fname)
			{
				keyname = va("%s (OpenType)", lfFaceName);
				if (!MyRegGetStringValue(HKEY_LOCAL_MACHINE, WinNT?"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts":"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts", keyname, fname, sizeof(fname)))
					*fname = 0;
			}

			R2D_Font_AddFontLink(fname, sizeof(fname), lfFaceName);
			Cvar_Set(&gl_font, fname);
		}
		return;
	}
#endif

	font_default = Font_LoadFont(8, gl_font.string);
	if (!font_default && *gl_font.string)
		font_default = Font_LoadFont(8, "");

	if (tsize != 8)
	{
		font_console = Font_LoadFont(tsize, gl_font.string);
		if (!font_console)
			font_console = Font_LoadFont(tsize, "");
	}
	if (!font_console)
		font_console = font_default;
}

static void QDECL R2D_Font_Callback(struct cvar_s *var, char *oldvalue)
{
	con_textsize.modified = true;
}

// console size manipulation callbacks
void R2D_Console_Resize(void)
{
	extern cvar_t gl_font;
	extern cvar_t vid_conwidth, vid_conheight;
	int cwidth, cheight;
	float xratio;
	float yratio=0;
	float ang, rad;
	extern cvar_t gl_screenangle;

	int fbwidth = vid.pixelwidth;
	int fbheight = vid.pixelheight;

	if (vid.framebuffer)
	{
		fbwidth = vid.framebuffer->width;
		fbheight = vid.framebuffer->height;
	}

	ang = (gl_screenangle.value>0?(gl_screenangle.value+45):(gl_screenangle.value-45))/90;
	ang = (int)ang * 90;
	if (ang)
	{
		rad = (ang * M_PI) / 180;
		vid.rotpixelwidth = fabs(cos(rad)) * (fbwidth) + fabs(sin(rad)) * (fbheight);
		vid.rotpixelheight = fabs(sin(rad)) * (fbwidth) + fabs(cos(rad)) * (fbheight);
	}
	else
	{
		vid.rotpixelwidth = fbwidth;
		vid.rotpixelheight = fbheight;
	}

	cwidth = vid_conwidth.value;
	cheight = vid_conheight.value;

	xratio = vid_conautoscale.value;
	if (xratio > 0)
	{
		char *s = strchr(vid_conautoscale.string, ' ');
		if (s)
			yratio = atof(s + 1);

		if (yratio <= 0)
			yratio = xratio;

		xratio = 1 / xratio;
		yratio = 1 / yratio;

		//autoscale overrides conwidth/height (without actually changing them)
		cwidth = vid.rotpixelwidth;
		cheight = vid.rotpixelheight;
	}
	else
	{
		xratio = 1;
		yratio = 1;
	}

	if (!cwidth && !cheight)
	{
		if (vid.dpi_y)
			cheight = (480 * 96) / vid.dpi_y;
		else
			cheight = 480;
	}
	if (cheight && !cwidth && vid.rotpixelheight)
		cwidth = (cheight*vid.rotpixelwidth)/vid.rotpixelheight;
	if (cwidth && !cheight && vid.rotpixelwidth)
		cheight = (cwidth*vid.rotpixelheight)/vid.rotpixelwidth;

	if (!cwidth)
		cwidth = vid.rotpixelwidth;
	if (!cheight)
		cheight = vid.rotpixelheight;

	cwidth*=xratio;
	cheight*=yratio;

	if (cwidth < 320)
		cwidth = 320;
	if (cheight < 200)
		cheight = 200;

	vid.width = cwidth;
	vid.height = cheight;

	Cvar_ForceCallback(&gl_font);

#ifdef PLUGINS
	Plug_ResChanged();
#endif
}

static void QDECL R2D_Conheight_Callback(struct cvar_s *var, char *oldvalue)
{
	if (var->value > 1536)	//anything higher is unreadable.
	{
		Cvar_ForceSet(var, "1536");
		return;
	}
	if (var->value < 200 && var->value)	//lower would be wrong
	{
		Cvar_ForceSet(var, "200");
		return;
	}

	R2D_Console_Resize();
}

static void QDECL R2D_Conwidth_Callback(struct cvar_s *var, char *oldvalue)
{
	//let let the user be too crazy
	if (var->value > 2048)	//anything higher is unreadable.
	{
		Cvar_ForceSet(var, "2048");
		return;
	}
	if (var->value < 320 && var->value)	//lower would be wrong
	{
		Cvar_ForceSet(var, "320");
		return;
	}

	R2D_Console_Resize();
}

static void QDECL R2D_Conautoscale_Callback(struct cvar_s *var, char *oldvalue)
{
	R2D_Console_Resize();
}

static void QDECL R2D_ScreenAngle_Callback(struct cvar_s *var, char *oldvalue)
{
	R2D_Console_Resize();
}


/*
============
R_PolyBlend
============
*/
//bright flashes and stuff, game only, doesn't touch sbar
void R2D_PolyBlend (void)
{
	if (!r_refdef.playerview->screentint[3])
		return;

	if (r_refdef.flags & RDF_NOWORLDMODEL)
		return;

	R2D_ImageColours (r_refdef.playerview->screentint[0], r_refdef.playerview->screentint[1], r_refdef.playerview->screentint[2], r_refdef.playerview->screentint[3]);
	R2D_ScalePic(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, shader_polyblend);
	R2D_ImageColours (1, 1, 1, 1);
}

//for lack of hardware gamma
void R2D_BrightenScreen (void)
{
	float f;

	RSpeedMark();

	if (fabs(v_contrast.value - 1.0) < 0.05 && fabs(v_brightness.value - 0) < 0.05 && fabs(v_gamma.value - 1) < 0.05)
		return;

	//don't go crazy with brightness. that makes it unusable and is thus unsafe - and worse, lots of people assume its based around 1 (like gamma and contrast are). cap to 0.5
	if (v_brightness.value > 0.5)
		v_brightness.value = 0.5;
	if (v_contrast.value < 0.5)
		v_contrast.value = 0.5;

	if (r2d_canhwgamma || vid_hardwaregamma.ival == 4)
		return;

	TRACE(("R2D_BrightenScreen: brightening\n"));
	if ((v_gamma.value != 1 || v_contrast.value > 3) && shader_gammacb->prog)
	{
		//this should really be done properly, with render-to-texture
		R2D_ImageColours (v_gamma.value, v_contrast.value, v_brightness.value, 1);
		R2D_Image(0, 0, vid.width, vid.height, 0, 0, 1, 1, shader_gammacb);
	}
	else
	{
		f = v_contrast.value;
		f = min (f, 3);

		while (f > 1)
		{
			if (f >= 2)
			{
				R2D_ImageColours (1, 1, 1, 1);
				f *= 0.5;
			}
			else
			{
				R2D_ImageColours (f - 1, f - 1, f - 1, 1);
				f = 1;
			}
			R2D_ScalePic(0, 0, vid.width, vid.height, shader_contrastup);
		}
		if (f < 1)
		{
			R2D_ImageColours (f, f, f, 1);
			R2D_ScalePic(0, 0, vid.width, vid.height, shader_contrastdown);
		}

		if (v_brightness.value)
		{
			R2D_ImageColours (v_brightness.value, v_brightness.value, v_brightness.value, 1);
			R2D_ScalePic(0, 0, vid.width, vid.height, shader_brightness);
		}
	}
	R2D_ImageColours (1, 1, 1, 1);

	/*make sure the hud is redrawn after if needed*/
	Sbar_Changed();

	RSpeedEnd(RSPEED_PALETTEFLASHES);
}

//for menus
void R2D_FadeScreen (void)
{
	R2D_ScalePic(0, 0, vid.width, vid.height, shader_menutint);

	Sbar_Changed();
}

//crosshairs
#define CS_HEIGHT 8
#define CS_WIDTH 8
unsigned char crosshair_pixels[] =
{
	// 2 + (spaced)
	0x08,
	0x00,
	0x08,
	0x55,
	0x08,
	0x00,
	0x08,
	0x00,
	// 3 +
	0x00,
	0x08,
	0x08,
	0x36,
	0x08,
	0x08,
	0x00,
	0x00,
	// 4 X
	0x00,
	0x22,
	0x14,
	0x00,
	0x14,
	0x22,
	0x00,
	0x00,
	// 5 X (spaced)
	0x41,
	0x00,
	0x14,
	0x00,
	0x14,
	0x00,
	0x41,
	0x00,
	// 6 diamond (unconnected)
	0x00,
	0x14,
	0x22,
	0x00,
	0x22,
	0x14,
	0x00,
	0x00,
	// 7 diamond
	0x00,
	0x08,
	0x14,
	0x22,
	0x14,
	0x08,
	0x00,
	0x00,
	// 8 four points
	0x00,
	0x08,
	0x00,
	0x22,
	0x00,
	0x08,
	0x00,
	0x00,
	// 9 three points
	0x00,
	0x00,
	0x08,
	0x00,
	0x22,
	0x00,
	0x00,
	0x00,
	// 10
	0x08,
	0x2a,
	0x00,
	0x63,
	0x00,
	0x2a,
	0x08,
	0x00,
	// 11
	0x49,
	0x2a,
	0x00,
	0x63,
	0x00,
	0x2a,
	0x49,
	0x00,
	// 12 horizontal line
	0x00,
	0x00,
	0x00,
	0x77,
	0x00,
	0x00,
	0x00,
	0x00,
	// 13 vertical line
	0x08,
	0x08,
	0x08,
	0x00,
	0x08,
	0x08,
	0x08,
	0x00,
	// 14 larger +
	0x08,
	0x08,
	0x08,
	0x77,
	0x08,
	0x08,
	0x08,
	0x00,
	// 15 angle
	0x00,
	0x00,
	0x00,
	0x70,
	0x08,
	0x08,
	0x08,
	0x00,
	// 16 dot
	0x00,
	0x00,
	0x00,
	0x08,
	0x00,
	0x00,
	0x00,
	0x00,
	// 17 weird angle thing
	0x00,
	0x00,
	0x00,
	0x38,
	0x48,
	0x08,
	0x10,
	0x00,
	// 18 circle w/ dot
	0x00,
	0x00,
	0x00,
	0x6b,
	0x41,
	0x63,
	0x3e,
	0x08,
	// 19 tripoint
	0x08,
	0x08,
	0x08,
	0x00,
	0x14,
	0x22,
	0x41,
	0x00,
};

void R2D_Crosshair_Update(void)
{
	int crossdata[CS_WIDTH*CS_HEIGHT];
	int c;
	int w, h;
	unsigned char *x;

	c = crosshair.ival;
	if (!crosshairimage.string)
		return;
	else if (crosshairimage.string[0] && c == 1)
	{
		shader_crosshair->defaulttextures->base = R_LoadHiResTexture (crosshairimage.string, "crosshairs", IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);
		if (TEXVALID(shader_crosshair->defaulttextures->base))
			return;
	}
	else if (c <= 1)
		return;

	c -= 2;
	c = c % (sizeof(crosshair_pixels) / (CS_HEIGHT*sizeof(*crosshair_pixels)));

	if (!TEXVALID(ch_int_texture))
		ch_int_texture = Image_CreateTexture("***crosshair***", NULL, IF_UIPIC|IF_NOMIPMAP);
	shader_crosshair->defaulttextures->base = ch_int_texture;

	Q_memset(crossdata, 0, sizeof(crossdata));

	x = crosshair_pixels + (CS_HEIGHT * c);
	for (h = 0; h < CS_HEIGHT; h++)
	{
		int pix = x[h];
		for (w = 0; w < CS_WIDTH; w++)
		{
			if (pix & 0x1)
				crossdata[CS_WIDTH * h + w] = 0xffffffff;
			pix >>= 1;
		}
	}

	Image_Upload(ch_int_texture, TF_RGBA32, crossdata, NULL, CS_WIDTH, CS_HEIGHT, IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA);

}

static void QDECL R2D_CrosshairImage_Callback(struct cvar_s *var, char *oldvalue)
{
	R2D_Crosshair_Update();
}

static void QDECL R2D_Crosshair_Callback(struct cvar_s *var, char *oldvalue)
{
	R2D_Crosshair_Update();
}

static void QDECL R2D_CrosshairColor_Callback(struct cvar_s *var, char *oldvalue)
{
	SCR_StringToRGB(var->string, ch_color, 255);

	ch_color[0] = bound(0, ch_color[0], 1);
	ch_color[1] = bound(0, ch_color[1], 1);
	ch_color[2] = bound(0, ch_color[2], 1);
}

void R2D_DrawCrosshair(void)
{
	int x, y;
	int sc;
	float sx, sy, sizex, sizey;

	float size;

	if (crosshair.ival < 1)
		return;

	// old style
	if (crosshair.ival == 1 && !crosshairimage.string[0])
	{
		// adjust console crosshair scale to match default
		size = crosshairsize.value;
		if (size == 0)
			size = 8;
		else if (size < 0)
			size = -size;
		for (sc = 0; sc < cl.splitclients; sc++)
		{
			SCR_CrosshairPosition(&cl.playerview[sc], &sx, &sy);
			Font_BeginScaledString(font_default, sx, sy, size, size, &sx, &sy);
			sx -= Font_CharScaleWidth(CON_WHITEMASK, '+' | 0xe000)/2;
			sy -= Font_CharScaleHeight()/2;
			R2D_ImageColours(ch_color[0], ch_color[1], ch_color[2], crosshairalpha.value);
			Font_DrawScaleChar(sx, sy, CON_WHITEMASK, '+' | 0xe000);
			Font_EndString(font_default);
		}
		R2D_ImageColours(1,1,1,1);
		return;
	}

	size = crosshairsize.value;

	if (size < 0)
	{
		size = -size;
		sizex = size;
		sizey = size;
	}
	else
	{
		sizex = (size*vid.rotpixelwidth) / (float)vid.width;
		sizey = (size*vid.rotpixelheight) / (float)vid.height;
	}

	sizex = (int)sizex;
	sizex = ((sizex)*(int)vid.width) / (float)vid.rotpixelwidth;

	sizey = (int)sizey;
	sizey = ((sizey)*(int)vid.height) / (float)vid.rotpixelheight;

	R2D_ImageColours(ch_color[0], ch_color[1], ch_color[2], crosshairalpha.value);
	for (sc = 0; sc < cl.splitclients; sc++)
	{
		SCR_CrosshairPosition(&cl.playerview[sc], &sx, &sy);

		//translate to pixel coord, for rounding
		x = ((sx-sizex+(sizex/CS_WIDTH))*vid.rotpixelwidth) / (float)vid.width;
		y = ((sy-sizey+(sizey/CS_HEIGHT))*vid.rotpixelheight) / (float)vid.height;

		//translate to screen coords
		sx = ((x)*(int)vid.width) / (float)vid.rotpixelwidth;
		sy = ((y)*(int)vid.height) / (float)vid.rotpixelheight;

		R2D_Image(sx, sy, sizex*2, sizey*2, 0, 0, 1, 1, shader_crosshair);
	}
	R2D_ImageColours(1, 1, 1, 1);
}

static texid_t internalrt;
//resize a texture for a render target and specify the format of it.
//pass TF_INVALID and sizes=0 to get without configuring (shaders that hardcode an $rt1 etc).
texid_t R2D_RT_Configure(const char *id, int width, int height, uploadfmt_t rtfmt, unsigned int imageflags)
{
	texid_t tid;
	if (!strcmp(id, "-"))
	{
		internalrt = tid = Image_CreateTexture("", NULL, imageflags);
	}
	else
	{
		tid = Image_FindTexture(id, NULL, imageflags);
		if (!TEXVALID(tid))
			tid = Image_CreateTexture(id, NULL, imageflags);
	}

	if (rtfmt)
	{
		tid->flags = (tid->flags & ~(IF_NEAREST|IF_LINEAR)) | (imageflags & (IF_NEAREST|IF_LINEAR));
		Image_Upload(tid, rtfmt, NULL, NULL, width, height, imageflags);
		tid->width = width;
		tid->height = height;
	}
	return tid;
}
texid_t R2D_RT_GetTexture(const char *id, unsigned int *width, unsigned int *height)
{
	texid_t tid;
	if (!strcmp(id, "-"))
	{
		tid = internalrt;
//		internalrt = r_nulltex;
	}
	else
		tid = Image_FindTexture(id, NULL, RT_IMAGEFLAGS);

	if (tid)
	{
		*width = tid->width;
		*height = tid->height;
	}
	else
	{
		*width = 0;
		*height = 0;
	}
	return tid;
}

#endif