#include "sys_render.h"
#include "system.h"
#include "sys_vr.h"
#include "sys_vr_passthru.h"
#include "sys_log.h"
#include "sys_quadstore.h"
#include "platform.h"
#include "ogl.h"
#include "scenefile.h"
#include "sys_tilestore.h"
#include "imgload.h"

#ifdef _DEBUG
#define GLERRCHECK {GLuint glerr = glGetError(); if(glerr) yrLog(0, "OpenGL error: %x", glerr);}
#else
#define GLERRCHECK
#endif

#define MAX_MODELS 32
#define MAX_BG_TEXTURE 125
#define MAX_TEXTURES 512
#define MAX_MODEL_RENDER 32
#define MAX_TRACKED 2
#define MAX_BEAMS MAX_TRACKED
#define MAX_TRACKED_SHADOW MAX_TRACKED
#define MAX_TRACKED_OVERLAY MAX_TRACKED
#define TRACKED_SHADOW_SIZE 0.3f
#define MAX_LIGHTS 2
#define TEXUNIT_SHADOW 4
#define Z_FAR (2048.0f)
#define Z_NEAR (0.125f)
static void set_light_uniforms(GLint screensize, GLint color, GLint dir, GLint trackedpos, GLint map);

static int yrRender_init(void* loadtoken);
static int yrRender_tick(void);
static int yrRender_save(void* savetoken, void* saveevent, int* savefailed) { yrEvent_set(saveevent, 1); return 0; }
static int yrRender_canfreeze(void) { return 1; }
static int yrRender_freeze(void) { return 0; }
static int yrRender_unfreeze(void) { return 0; }
static int yrRender_shutdown(void);

void yrRender_reg(void)
{
	yrSystem_register(sysRender,
					  yrRender_init,
					  yrRender_tick,
					  yrRender_save,
					  yrRender_canfreeze,
					  yrRender_freeze,
					  yrRender_unfreeze,
					  yrRender_shutdown,
					  (1<<sysLog)|(1<<sysVR)|(1<<sysDesktop));
}

struct yr_texture
{
	unsigned int next;
	GLuint tex;
};

struct yr_model
{
	GLuint vbo;
	GLuint ibo;
	GLuint vao;
	GLuint icount;
	unsigned int next;
};

struct yr_modelvtx
{
	float x;
	float y;
	float z;
	float nx;
	float ny;
	float nz;
	float u;
	float v;
};

struct yr_rendertoken
{
	mat4f mat;
	vec4i pos;
	GLuint vao;
	GLuint vbo;
	GLuint ibo;
	GLuint tex;
	GLuint icount;
	yrColor color;
	uint32_t tracked_id;
};

/************
* All Shaders
*************/
extern const GLchar* VtxShader_unit2screen;
extern const GLchar* VtxShader_floor;
extern const GLchar* VtxShader_quad;
extern const GLchar* VtxShader_model;
extern const GLchar* VtxShader_beam;
extern const GLchar* VtxShader_beamcap;
extern const GLchar* VtxShader_tparc;
extern const GLchar* VtxShader_tpfloor;
extern const GLchar* VtxShader_overlay_render;
extern const GLchar* VtxShader_overlay_draw;
extern const GLchar* VtxShader_overlay_erase;
extern const GLchar* VtxShader_trackedoverlay_render;
extern const GLchar* VtxShader_trackedoverlay_draw;
extern const GLchar* VtxShader_barstat;
extern const GLchar* VtxShader_colorpick_rgb;
extern const GLchar* VtxShader_colorpick_lum;
extern const GLchar* VtxShader_modelshadow;

extern const GLchar* FragShader_sky;
extern const GLchar* FragShader_floor;
extern const GLchar* FragShader_quad;
extern const GLchar* FragShader_model;
extern const GLchar* FragShader_beam;
extern const GLchar* FragShader_beamcap;
extern const GLchar* FragShader_tparc;
extern const GLchar* FragShader_tpfloor;
extern const GLchar* FragShader_overlay_render;
extern const GLchar* FragShader_overlay_draw;
extern const GLchar* FragShader_overlay_erase;
extern const GLchar* FragShader_trackedoverlay_render;
extern const GLchar* FragShader_barstat;
extern const GLchar* FragShader_colorpick_rgb;
extern const GLchar* FragShader_colorpick_lum;

/***************************
* Shared rendering resources
****************************/
static int render_setup(void);
static void render_frame_pre(int lr);
static void render_frame_post(void);
static void render_cleanup(void);

static struct {
	GLuint vao;
	GLuint vbo;
}
unitquad;
static struct {
	yrTexture skytex;
	yrTexture floortex;
	GLuint prog_sky;
	GLuint prog_floor;
	GLint uni_s_cam_mat;
	GLint uni_s_light_screensize;
	GLint uni_f_cam_mat;
	GLint uni_f_cam_off;
	GLint uni_f_light_screensize;
	GLint uni_f_light_dir;
	GLint uni_f_light_color;
	GLint uni_f_light_trackedpos;
	GLint uni_f_light_map;
}
sky = {0};
static struct {
	vec4i offset;
	struct {
		mat4f left;
		mat4f right;
	}
	eye_from_hmd, eye_from_world;
}
camera = {0};
static struct {
	uint32_t width;
	uint32_t height;
	struct _yr_rt {
		GLuint render_fb;
		GLuint render_tex;
		GLuint render_depth;
		GLuint present_fb;
		GLuint present_tex;
		Texture_t vrtex;
	}
	left, right;
}
rendertarget = {0};
static struct {
	struct {
		GLuint vbo;
		GLuint vao;
		GLuint count;
	}
	mesh[2];
	GLuint prog;
}
hiddenarea = {0};
static struct {
	size_t count;
	GLuint ssbo;
	yrTexture tex[MAX_BG_TEXTURE+1];
	uint64_t handle[MAX_BG_TEXTURE+1];
	uint32_t w[MAX_BG_TEXTURE+1];
	uint32_t h[MAX_BG_TEXTURE+1];
	uint32_t map_from_idx[MAX_BG_TEXTURE+1];
}
quadbg = {0};
static struct {
	vec4f dir;
	vec4f color;
}
lightdata[MAX_LIGHTS] = {0};

static int render_setup(void)
{
	//render targets
	ovrSystem_GetRecommendedRenderTargetSize(&rendertarget.width, &rendertarget.height);
	for(int lr = 0; lr < 2; ++lr) {
		rendertarget.right = rendertarget.left;
		//multisample render buffer
		GLuint fb = 0;
		GLuint tex = 0;
		GLuint depth = 0;
		GLsizei w = rendertarget.width;
		GLsizei h = rendertarget.height;
		glGenFramebuffers(1, &fb);
		glGenTextures(1, &tex);
		glGenRenderbuffers(1, &depth);
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
		glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_SRGB8_ALPHA8, w, h, GL_TRUE);
		glBindRenderbuffer(GL_RENDERBUFFER, depth);
		glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT, w, h);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
		GLenum drawbuffers = GL_COLOR_ATTACHMENT0;
		glDrawBuffers(1, &drawbuffers);

		int fb_ok = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
		glBindRenderbuffer(GL_RENDERBUFFER, 0);
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		if(fb_ok) {
			rendertarget.left.render_fb = fb;
			rendertarget.left.render_tex = tex;
			rendertarget.left.render_depth = depth;
		}
		else {
			glDeleteRenderbuffers(1, &depth);
			glDeleteTextures(1, &tex);
			glDeleteFramebuffers(1, &fb);
			yrLog(0, "OpenGL error while creating %s rendertarget: %x", lr ? "left" : "right", glGetError());
			return -1;
		}
		//ordinary renderbuffer to pass to OpenVR
		fb = 0;
		tex = 0;
		glGenFramebuffers(1, &fb);
		glGenTextures(1, &tex);
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glBindTexture(GL_TEXTURE_2D, tex);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
		glTexStorage2D(GL_TEXTURE_2D, 1, GL_SRGB8_ALPHA8, w, h);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
		
		fb_ok = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
		glBindTexture(GL_TEXTURE_2D, 0);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		if(fb_ok) {
			rendertarget.left.present_fb = fb;
			rendertarget.left.present_tex = tex;
		}
		else {			
			glDeleteTextures(1, &tex);
			glDeleteFramebuffers(1, &fb);
			yrLog(0, "OpenGL error while creating %s renderbuffer: %x", lr ? "left" : "right", glGetError());
			return -1;
		}
		GLenum glerr = glGetError();
		if(glerr != GL_NO_ERROR) {
			yrLog(0, "OpenGL error during hidden area mesh VBO creation: %x", glerr);
			return -1;
		}
		//setup the shared texture
		rendertarget.left.vrtex.handle = (void*) (size_t) rendertarget.left.present_tex;
		rendertarget.left.vrtex.eColorSpace = EColorSpace_ColorSpace_Gamma;
		rendertarget.left.vrtex.eType = ETextureType_TextureType_OpenGL;

		//hidden area mesh
		HiddenAreaMesh_t ham = ovrSystem_GetHiddenAreaMesh((lr == 0) ? EVREye_Eye_Left : EVREye_Eye_Right);
		hiddenarea.mesh[lr].count = ham.unTriangleCount * 3;
		glGenBuffers(1, &hiddenarea.mesh[lr].vbo);
		glBindBuffer(GL_ARRAY_BUFFER, hiddenarea.mesh[lr].vbo);
		glBufferData(GL_ARRAY_BUFFER, ham.unTriangleCount * 3 * 2 * sizeof(float), ham.pVertexData, GL_STATIC_DRAW);
		glerr = glGetError();
		if(glerr != GL_NO_ERROR) {
			yrLog(0, "OpenGL error during renderbuffer set-up: %x", glerr);
			return -1;
		}

		glGenVertexArrays(1, &hiddenarea.mesh[lr].vao);
		glBindVertexArray(hiddenarea.mesh[lr].vao);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*) 0);
		glEnableVertexAttribArray(0);
		glBindVertexArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glerr = glGetError();
		if(glerr != GL_NO_ERROR) {
			yrLog(0, "OpenGL error during hidden area mesh VAO creation: %x", glerr);
			return -1;
		}
	}

	//hidden area shader
	int err = 0;
	GLenum glerr;
	hiddenarea.prog = glCreateProgram(); //just use default everything
	err = OpenGL_shader(hiddenarea.prog, GL_VERTEX_SHADER, VtxShader_unit2screen);	if(err) { yrLog(0, "Compilation error(s) were in hidden area mesh vertex shader."); return -1; }
	glLinkProgram(hiddenarea.prog);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Hidden area mesh shaderprogram failed to link: %x", glerr); return -1; }

	//unitquad
	glGenVertexArrays(1, &unitquad.vao);
	glGenBuffers(1, &unitquad.vbo);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), NULL, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, FALSE, 0, (GLvoid*)0);
	glEnableVertexAttribArray(0);

	float* buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	if(!buf) {
		yrLog(0, "Could not map OpenGL buffer: %x", glGetError());
		return -1;
	}
	buf[0*2 + 0] = 0.0f;
	buf[0*2 + 1] = 0.0f;
	buf[1*2 + 0] = 1.0f;
	buf[1*2 + 1] = 0.0f;
	buf[2*2 + 0] = 1.0f;
	buf[2*2 + 1] = 1.0f;
	buf[3*2 + 0] = 0.0f;
	buf[3*2 + 1] = 1.0f;
	glUnmapBuffer(GL_ARRAY_BUFFER);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during unitquad buffer write: %x", glGetError());
		return -1;
	}

	//sky and floor
	err = 0;
	sky.prog_sky = glCreateProgram();
	err = OpenGL_shader(sky.prog_sky, GL_VERTEX_SHADER, VtxShader_unit2screen);	if(err) { yrLog(0, "Compilation error(s) were in sky vertex shader."); return -1; }
	err = OpenGL_shader(sky.prog_sky, GL_FRAGMENT_SHADER, FragShader_sky);		if(err) { yrLog(0, "Compilation error(s) were in sky fragment shader."); return -1; }
	glLinkProgram(sky.prog_sky);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Sky shaderprogram failed to link: %x", glerr); return -1; }

	sky.prog_floor = glCreateProgram();
	err = OpenGL_shader(sky.prog_floor, GL_VERTEX_SHADER, VtxShader_floor);		if(err) { yrLog(0, "Compilation error(s) were in floor vertex shader."); return -1; }
	err = OpenGL_shader(sky.prog_floor, GL_FRAGMENT_SHADER, FragShader_floor);	if(err) { yrLog(0, "Compilation error(s) were in floor fragment shader."); return -1; }
	glLinkProgram(sky.prog_floor);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Floor shaderprogram failed to link: %x", glerr); return -1; }

	sky.uni_s_cam_mat			= glGetUniformLocation(sky.prog_sky, "cam_mat");
	sky.uni_s_light_screensize	= glGetUniformLocation(sky.prog_sky, "light_screensize");
	sky.uni_f_cam_mat			= glGetUniformLocation(sky.prog_floor, "cam_mat");
	sky.uni_f_cam_off			= glGetUniformLocation(sky.prog_floor, "cam_off");
	sky.uni_f_light_screensize	= glGetUniformLocation(sky.prog_floor, "light_screensize");
	sky.uni_f_light_dir			= glGetUniformLocation(sky.prog_floor, "light_dir");
	sky.uni_f_light_color		= glGetUniformLocation(sky.prog_floor, "light_color");
	sky.uni_f_light_trackedpos	= glGetUniformLocation(sky.prog_floor, "light_trackedpos");
	sky.uni_f_light_map			= glGetUniformLocation(sky.prog_floor, "light_map");
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during sky or floor uniform location acquisition: %x", glerr);
		return -1;
	}

	//camera
	vec4i zero = {0,0,0,0};
	camera.offset = zero;

	mat4f proj_left = mat44f_rows_convert((float*)ovrSystem_GetProjectionMatrix(EVREye_Eye_Left, Z_NEAR, Z_FAR).m);
	mat4f proj_right = mat44f_rows_convert((float*)ovrSystem_GetProjectionMatrix(EVREye_Eye_Right, Z_NEAR, Z_FAR).m);
	mat4f off_left = mat4f_invert(mat34f_rows_convert((const float*)ovrSystem_GetEyeToHeadTransform(EVREye_Eye_Left).m));
	mat4f off_right = mat4f_invert(mat34f_rows_convert((const float*)ovrSystem_GetEyeToHeadTransform(EVREye_Eye_Right).m));
	camera.eye_from_hmd.left = mat4f_combine(proj_left, off_left);
	camera.eye_from_hmd.right = mat4f_combine(proj_right, off_right);

	//opengl
	glClearDepth(1.0f);
	glDepthFunc(GL_LEQUAL);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during OpenGL state init: %x", glerr);
		return -1;
	}

	//quad backgrounds
	memset(quadbg.map_from_idx, 0, sizeof(quadbg.map_from_idx));

	glGenBuffers(1, &quadbg.ssbo);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, quadbg.ssbo);
	glBufferData(GL_SHADER_STORAGE_BUFFER, (MAX_BG_TEXTURE+1) * sizeof(uint64_t), NULL, GL_STATIC_DRAW);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during quad background SSBO creation: %x", glerr);
		return -1;
	}

	//lights
	lightdata[0].dir.x = 0.0f;
	lightdata[0].dir.y = 0.0f;
	lightdata[0].dir.z = -1.0f;
	lightdata[0].dir.w = 0.0f;
	//lightdata[0].dir = vec3f_normalized(lightdata[0].dir);
	lightdata[0].color.c[0] = 0.9f;
	lightdata[0].color.c[1] = 0.95f;
	lightdata[0].color.c[2] = 1.0f;
	lightdata[0].color.c[3] = 1.0f;

	lightdata[1].dir.x = 1.0f;
	lightdata[1].dir.y = 0.0f;
	lightdata[1].dir.z = -1.0f;
	lightdata[1].dir.w = 0.0f;
	lightdata[1].dir = vec3f_normalized(lightdata[1].dir);
	lightdata[1].color.c[0] = 1.0f;
	lightdata[1].color.c[1] = 0.95f;
	lightdata[1].color.c[2] = 0.9f;
	lightdata[1].color.c[3] = 1.0f;
	return 0;
}

static int render_load(void* statictoken)
{
	yrSceneFile* sf = yrSystem_scenefile();
	unsigned bg_count;
	size_t* bg_size = NULL;
	char* bg_blob = NULL;
	uint32_t lastbgid;
	int err = yrSceneFile_backgrounds_get(sf, &bg_count, &bg_size, (void**)&bg_blob, &lastbgid, quadbg.map_from_idx);
	if(err) return err;

	if(bg_count > MAX_BG_TEXTURE) bg_count = MAX_BG_TEXTURE;
	size_t bg_off = 0;
	for(unsigned i = 0; i < bg_count; ++i)
	{
		//decode image
		quadbg.tex[i] = yrTexture_loadmem(bg_size[i], bg_blob + bg_off);
		bg_off += bg_size[i];
		quadbg.count += 1;
		if(!quadbg.tex[i]) yrLog(0, "Background image ignored.");

		//get handle
		struct yr_texture* tex = quadbg.tex[i];
		quadbg.handle[i] = tex ? glGetTextureHandleARB(tex->tex) : 0;
		glMakeTextureHandleResidentARB(quadbg.handle[i]);
		{ GLenum glerr = glGetError(); if(glerr != GL_NO_ERROR) yrLog(1, "Background image ignored, OpenGL error: %x", glerr); }

		//get size
		GLint w,h;
		glBindTexture(GL_TEXTURE_2D, tex ? tex->tex : 0);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
		glBindTexture(GL_TEXTURE_2D, 0);
		quadbg.w[i] = (uint32_t) w;
		quadbg.h[i] = (uint32_t) h;
		{ GLenum glerr = glGetError();
		if(glerr != GL_NO_ERROR) {
			yrLog(1, "Background image size undetectable, OpenGL error: %x", glerr);
			quadbg.w[i] = 512;
			quadbg.h[i] = 512;
		}}
	}
	//done with image data
	free(bg_size); bg_size = NULL;
	free(bg_blob); bg_blob = NULL;	

	//clean other entries
	for(size_t i = bg_count; i < MAX_BG_TEXTURE + 1; ++i) {
		quadbg.handle[i] = 0;
		quadbg.tex[i] = 0;
		quadbg.w[i] = 512;
		quadbg.h[i] = 512;
	}

	//copy handles to opengl buffer
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, quadbg.ssbo);
	void* buf = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY);
	memcpy(buf, quadbg.handle, (MAX_BG_TEXTURE + 1) * sizeof(uint64_t));
	glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	{ GLenum glerr = glGetError(); if(glerr != GL_NO_ERROR) { yrLog(0, "OpenGL error during background image handle copy: %x", glerr); return -1;}}

	//load sky and floor
	sky.skytex = yrTexture_loadfile("data/vr/bg_sky.png");
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)sky.skytex)->tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);
	sky.floortex = yrTexture_loadfile("data/vr/bg_floor.png");
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)sky.floortex)->tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);

	return 0;
}

static void render_cleanup(void)
{
	//remove opengl bindings
	glUseProgram(0);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	glBindVertexArray(0);

	//quad backgrounds
	glDeleteBuffers(1, &quadbg.ssbo);
	quadbg.ssbo = 0;
	quadbg.count = 0; //textures should have already been cleaned up by modeltex_cleanup

	//sky
	glDeleteProgram(sky.prog_sky);
	glDeleteProgram(sky.prog_floor);
	sky.prog_sky = 0;
	sky.prog_floor = 0;
	sky.skytex = 0;
	sky.floortex = 0;

	//unitquad
	glDeleteBuffers(1, &unitquad.vbo);
	glDeleteVertexArrays(1, &unitquad.vao);

	//ham shader
	glDeleteProgram(hiddenarea.prog);

	//render targets
	for(int lr = 0; lr < 2; ++lr)
	{
		glDeleteVertexArrays(1, &hiddenarea.mesh[lr].vao);
		glDeleteBuffers(1, &hiddenarea.mesh[lr].vbo);
		hiddenarea.mesh[lr].count = 0;

		if(rendertarget.left.present_fb) {			
			glDeleteTextures(1, &rendertarget.left.present_tex);
			glDeleteFramebuffers(1, &rendertarget.left.present_fb);
		}
		if(rendertarget.left.render_fb) {
			glDeleteRenderbuffers(1, &rendertarget.left.render_depth);
			glDeleteTextures(1, &rendertarget.left.render_tex);
			glDeleteFramebuffers(1, &rendertarget.left.render_fb);
		}
		rendertarget.left = rendertarget.right;
	}
	memset(&rendertarget, 0, sizeof(rendertarget));
}

static void render_frame_pre(int lr)
{
	if(lr == 0) {
		//update camera
		camera.offset = yrVR_get_coord_offset();
		mat4f hmd_from_world = yrVR_hmd_from_world();
		camera.eye_from_world.left = mat4f_combine(camera.eye_from_hmd.left, hmd_from_world);
		camera.eye_from_world.right = mat4f_combine(camera.eye_from_hmd.right, hmd_from_world);

		//move second light behind user	
		vec4f ltarget = vec4f_neg(yrVR_world_from_tracked(0).col[2]);
		if(!ltarget.x && !ltarget.y) ltarget.x = 1.0f;
		ltarget.z = 0.0f;
		ltarget = vec3f_normalized(ltarget);
		ltarget.z = -1.0f;
		ltarget = vec3f_normalized(ltarget);
		lightdata[1].dir = vec3f_normalized(vec4f_add(vec4f_mul(255.0f, lightdata[1].dir), ltarget));
	}
	struct _yr_rt* rt = lr ? &rendertarget.left : &rendertarget.right;
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;

	//hidden area mesh and depth clear
	glEnable(GL_FRAMEBUFFER_SRGB);
	glDisable(GL_MULTISAMPLE);
	glDisable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	glColorMask(0,0,0,0);
	glUseProgram(hiddenarea.prog);
	//draw
	glBindFramebuffer(GL_FRAMEBUFFER, rt->render_fb);
	glViewport(0, 0, rendertarget.width, rendertarget.height);
	glClear(GL_DEPTH_BUFFER_BIT);
	glBindBuffer(GL_ARRAY_BUFFER, hiddenarea.mesh[lr].vbo);
	glBindVertexArray(hiddenarea.mesh[lr].vao);
	glDrawArrays(GL_TRIANGLES, 0, hiddenarea.mesh[lr].count);
	
	glColorMask(1,1,1,1);

	//draw sky
	glEnable(GL_MULTISAMPLE);
	glDepthMask(0);

	glUseProgram(sky.prog_sky);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindVertexArray(unitquad.vao);
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)sky.skytex)->tex);
	GLERRCHECK
	
	glUniform2i(sky.uni_s_light_screensize, (GLint) rendertarget.width, (GLint) rendertarget.height);

	//draw
	glUniformMatrix4fv(sky.uni_s_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

	glDepthMask(1);

	GLERRCHECK
		
	//draw floor
	glDepthFunc(GL_LEQUAL);
	glUseProgram(sky.prog_floor);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindVertexArray(unitquad.vao);
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)sky.floortex)->tex);
	GLERRCHECK

	glUniform4iv(sky.uni_f_cam_off, 1, (const GLint*) camera.offset.c);
	set_light_uniforms(sky.uni_f_light_screensize,
					   sky.uni_f_light_color,
					   sky.uni_f_light_dir,
					   sky.uni_f_light_trackedpos,
					   sky.uni_f_light_map);

	//draw
	glUniformMatrix4fv(sky.uni_f_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	
	GLERRCHECK
}

static void render_frame_post()
{
	//submit frame
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glDisable(GL_MULTISAMPLE);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
	struct _yr_rt* rt = &rendertarget.left;
	glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->render_fb);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rt->present_fb);
	glBlitFramebuffer(0, 0, rendertarget.width, rendertarget.height,
					  0, 0, rendertarget.width, rendertarget.height,
					  GL_COLOR_BUFFER_BIT, GL_LINEAR);
	rt = &rendertarget.right;
	glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->render_fb);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rt->present_fb);
	glBlitFramebuffer(0, 0, rendertarget.width, rendertarget.height,
					  0, 0, rendertarget.width, rendertarget.height,
					  GL_COLOR_BUFFER_BIT, GL_LINEAR);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

	ovrCompositor_Submit(EVREye_Eye_Left, &rendertarget.left.vrtex, NULL, EVRSubmitFlags_Submit_Default);
	ovrCompositor_Submit(EVREye_Eye_Right, &rendertarget.right.vrtex, NULL, EVRSubmitFlags_Submit_Default);
	//glFlush();
	
	GLERRCHECK
}

size_t yrRender_get_bgcount(void)
{
	return quadbg.count;
}

int yrRender_get_bgdata(size_t idx, yrTexture* tex, size_t* w, size_t* h)
{
	if(idx >= quadbg.count) return 0;
	*tex = quadbg.tex[idx];
	*w = (size_t) quadbg.w[idx];
	*h = (size_t) quadbg.h[idx];
	return 1;
}

unsigned char yrRender_bg_from_id(uint32_t id)
{
	if(id == 0) return MAX_BG_TEXTURE;
	for(unsigned char i = 0; i < MAX_BG_TEXTURE; ++i)
		if(quadbg.map_from_idx[i] == id)
			return i;
	return MAX_BG_TEXTURE;
}

uint32_t yrRender_bg_to_id(unsigned char idx) { return quadbg.map_from_idx[idx]; }

unsigned yrRender_mirrortex(void)
{
	return rendertarget.left.present_tex;
}

/***************
* Quad Rendering
****************/
static int render_quad_setup(void);
static void render_quad_frame(int lr);
static void render_quad_cleanup(void);

static struct
{
	size_t max_quad;
	size_t max_texture;
	size_t count;
	GLuint ssbo;
	GLuint prog;
	GLint uni_camera_mat;
	GLint uni_camera_pos;
	GLint uni_light_screensize;
	GLint uni_light_dir;
	GLint uni_light_color;
	GLint uni_light_trackedpos;
	GLint uni_light_map;
	float t_quadprep;
}
quads = {0};

static int render_quad_setup(void)
{
	quads.max_quad = 8192; //yrSystem_visible_quads(); //TODO: hardcoded: bad, don't let visible quads ever go above
	quads.max_texture = (16*1024*1024 - (sizeof(yrQuad) * quads.max_quad))/sizeof(uint64_t); //the SSBO will be 16MB large
	quads.count = 0;

	int err = 0;
	quads.prog = glCreateProgram();
	err = OpenGL_shader(quads.prog, GL_VERTEX_SHADER, VtxShader_quad);		if(err) { yrLog(0, "Compilation error(s) were in quads vertex shader."); return -1; }
	err = OpenGL_shader(quads.prog, GL_FRAGMENT_SHADER, FragShader_quad);	if(err) { yrLog(0, "Compilation error(s) were in quads fragment shader."); return -1; }
	glLinkProgram(quads.prog);
	GLenum glerr = glGetError();											if(glerr != GL_NO_ERROR) { yrLog(0, "Quads shaderprogram failed to link: %x", glerr); return -1; }

	quads.uni_camera_mat = glGetUniformLocation(quads.prog, "cam_mat");
	quads.uni_camera_pos = glGetUniformLocation(quads.prog, "cam_pos");
	quads.uni_light_screensize	= glGetUniformLocation(quads.prog, "light_screensize");
	quads.uni_light_dir			= glGetUniformLocation(quads.prog, "light_dir");
	quads.uni_light_color		= glGetUniformLocation(quads.prog, "light_color");
	quads.uni_light_trackedpos	= glGetUniformLocation(quads.prog, "light_trackedpos");
	quads.uni_light_map			= glGetUniformLocation(quads.prog, "light_map");

	glGenBuffers(1, &quads.ssbo);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, quads.ssbo);
	glBufferData(GL_SHADER_STORAGE_BUFFER, 16 * 1024 * 1024, NULL, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during quads SSBO creation: %x", glerr);
		return -1;
	}

	return 0;
}

static void render_quad_cleanup(void)
{
	glUseProgram(0);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
	
	glDeleteProgram(quads.prog);
	glDeleteBuffers(1, &quads.ssbo);

	quads.prog = 0;
	quads.ssbo = 0;

	quads.count = 0;
	quads.max_texture = 0;
	quads.max_quad = 0;
}

static void render_quad_frame(int lr)
{
	if(lr == 0)
	{
		uint64_t t_prep_a = yr_get_microtime();
		//fill up ssbo with the quads to render
		//TODO: probably don't even need to update this every frame because the quadtop isn't either
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, quads.ssbo);
		void* buf = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY); //TODO: replace with permanently mapped buffers, perhaps?

		uint32_t* offset_base = (uint32_t*)((yrQuad*)buf + quads.max_quad);
		uint64_t* texture_base = (uint64_t*)(offset_base + quads.max_quad);
		memset(texture_base, 0, 64 * sizeof(uint64_t));
		uint32_t texture_off = 64;

		yrQuad* q;
		float priority;
		quads.count = 0;
		yrQuad_iter i = yrQuad_iter_top();
		while(q = yrQuad_iter_next(&i, &priority))
		{
			yrQuad* ptr = (yrQuad*)buf + quads.count;
			*ptr = *q;

			uint32_t ttoff = q->ext->tile_max_idx;
			offset_base[quads.count] = texture_off;

			//lookup draw tiles
			size_t empty = 0;
			int do_lookup = !(q->flags & QUAD_TILECACHED) || (q->flags & QUAD_CLEARCACHE);
			uint32_t* tiles = q->ext->tiles;
			uint32_t* indices = q->ext->cached_tileidx;
		
			for(uint32_t i = 0; i < ttoff; ++i) {
				if(tiles[i] != 0xFFFFFFFFul) {
					TileID id = {tiles[i]};
					TileIndex* hint = (TileIndex*)(indices + i);
					texture_base[texture_off++] = yrTileStore_request(id, priority, hint, do_lookup);
				} else {
					texture_base[texture_off++] = 0;
					empty += 1;
				}
			}
			//all tiles empty, don't use up space in the texture array
			if(empty == ttoff) {
				offset_base[quads.count] = 0;
				texture_off -= (uint32_t)empty;
			}
			//filled up the buffer, no need to map anymore
			if(texture_off + 64 > quads.max_texture) break;
			//done
			q->flags |= QUAD_TILECACHED;
			quads.count += 1;
		}
		//no space left in tex buffer
		while(q = yrQuad_iter_next(&i, &priority))
		{
			yrQuad* ptr = (yrQuad*)buf + quads.count;
			*ptr = *q;
			offset_base[quads.count] = 0;
			q->flags |= QUAD_TILECACHED;
			q->flags &= ~QUAD_CLEARCACHE;
			quads.count += 1;
		}
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
		uint64_t t_prep_b = yr_get_microtime();
		quads.t_quadprep = (t_prep_b - t_prep_a)/1000000.0f;
	}

	//prepare context
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
	glUseProgram(quads.prog);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, quads.ssbo);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, quadbg.ssbo);

	set_light_uniforms(quads.uni_light_screensize,
					   quads.uni_light_color,
					   quads.uni_light_dir,
					   quads.uni_light_trackedpos,
					   quads.uni_light_map);
	
	//render the quads
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	glUniform4iv(quads.uni_camera_pos, 1, (const GLint*) camera.offset.c);
	glUniformMatrix4fv(quads.uni_camera_mat, 1, GL_FALSE, (const GLfloat*) cam);
	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, (GLsizei) quads.count);

	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);

	GLERRCHECK
}

/********************
* Models and textures
*********************/
static int render_modeltex_setup(void);
static void render_modeltex_frame(int lr);
static void render_modeltex_cleanup(void);

static struct {
	struct yr_model store[MAX_MODELS];
	struct yr_model* nextfree;
	struct yr_rendertoken queue[MAX_MODEL_RENDER];
	size_t queue_count;

	GLuint prog;
	GLint uni_cam_mat;
	GLint uni_cam_pos;
	GLint uni_mdl_mat;
	GLint uni_mdl_pos;
	GLint uni_mdl_color;
	GLint uni_light_screensize;
	GLint uni_light_dir;
	GLint uni_light_color;
}
models = {0};
static struct {
	struct yr_texture store[MAX_TEXTURES];
	struct yr_texture* nextfree;
}
textures = {0};
static struct {
	struct {
		mat4f pos;
		uint32_t id;
		struct {
			mat4f mat;
			GLuint texture;
			GLuint framebuffer;
			GLuint64 thandle;
		}
		map[MAX_LIGHTS];
	}
	tracked[MAX_TRACKED_SHADOW];
	GLuint prog;
	GLint uni_light_mat;
	GLint uni_mdl_mat;
}
modelshadow = {0};

static int render_modeltex_setup(void)
{
	//textures
	for(unsigned i = 0; i < MAX_TEXTURES; ++i) {
		textures.store[i].next = i + 1;
	}
	textures.store[MAX_TEXTURES - 1].next = 0xFFFFFFFFul;
	textures.nextfree = textures.store;

	//models
	for(unsigned i = 0; i < MAX_MODELS; ++i) {
		models.store[i].next = i + 1;
	}
	models.store[MAX_MODELS - 1].next = 0xFFFFFFFFul;
	models.nextfree = models.store;
	models.queue_count = 0;

	int err = 0;
	models.prog = glCreateProgram();
	err = OpenGL_shader(models.prog, GL_VERTEX_SHADER, VtxShader_model);	if(err) { yrLog(0, "Compilation error(s) were in models vertex shader."); return -1; }
	err = OpenGL_shader(models.prog, GL_FRAGMENT_SHADER, FragShader_model);	if(err) { yrLog(0, "Compilation error(s) were in models fragment shader."); return -1; }
	glLinkProgram(models.prog);
	GLenum glerr = glGetError();											if(glerr != GL_NO_ERROR) { yrLog(0, "Models shaderprogram failed to link: %x", glerr); return -1; }

	models.uni_cam_mat		= glGetUniformLocation(models.prog, "cam_mat");
	models.uni_cam_pos		= glGetUniformLocation(models.prog, "cam_pos");
	models.uni_mdl_color	= glGetUniformLocation(models.prog, "mdl_color");
	models.uni_mdl_mat		= glGetUniformLocation(models.prog, "mdl_mat");
	models.uni_mdl_pos		= glGetUniformLocation(models.prog, "mdl_pos");
	models.uni_light_screensize = glGetUniformLocation(models.prog, "light_screensize");
	models.uni_light_dir	= glGetUniformLocation(models.prog, "light_dir");
	models.uni_light_color	= glGetUniformLocation(models.prog, "light_color");

	//shadow maps
	err = 0;
	modelshadow.prog = glCreateProgram();
	err = OpenGL_shader(modelshadow.prog, GL_VERTEX_SHADER, VtxShader_modelshadow);		if(err) { yrLog(0, "Compilation error(s) were in model shadow vertex shader."); return -1; }
	glLinkProgram(modelshadow.prog);
	glerr = glGetError();		if(glerr != GL_NO_ERROR) { yrLog(0, "Model shadow shaderprogram failed to link: %x", glerr); return -1; }

	modelshadow.uni_light_mat		= glGetUniformLocation(modelshadow.prog, "light_mat");
	modelshadow.uni_mdl_mat		= glGetUniformLocation(modelshadow.prog, "mdl_mat");

	for(size_t t = 0; t < MAX_TRACKED_SHADOW; ++t)
	for(size_t l = 0; l < MAX_LIGHTS; ++l)
	{
		modelshadow.tracked[t].id = 0xFFFFFFFFul;

		glGenFramebuffers(1, &modelshadow.tracked[t].map[l].framebuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, modelshadow.tracked[t].map[l].framebuffer);
		glGenTextures(1, &modelshadow.tracked[t].map[l].texture);
		glBindTexture(GL_TEXTURE_2D, modelshadow.tracked[t].map[l].texture);
		glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT16, 1024, 1024);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		GLuint64 handle = glGetTextureHandleARB(modelshadow.tracked[t].map[l].texture);
		modelshadow.tracked[t].map[l].thandle = handle;
		glMakeTextureHandleResidentARB(handle);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, modelshadow.tracked[t].map[l].texture, 0);
		GLenum drawbuffers = GL_NONE;
		glDrawBuffers(1, &drawbuffers);
		glBindTexture(GL_TEXTURE_2D, 0);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glerr = glGetError();
		if(glerr != GL_NO_ERROR) { yrLog(0, "OpenGL error occured during tracked shadowmap creation: %x", glerr); return -1; }
		if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
			yrLog(0, "Tracked shadowmap framebuffer creation failed.");
			return -1;
		}
	}

	return 0;
}

static void render_modeltex_cleanup(void)
{
	glUseProgram(0);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	//shadowmaps
	glActiveTexture(GL_TEXTURE0);
	for(size_t t = 0; t < MAX_TRACKED_SHADOW; ++t)
	for(size_t l = 0; l < MAX_LIGHTS; ++l)
	{
		glDeleteFramebuffers(1, &modelshadow.tracked[t].map[l].framebuffer);
		GLuint64 handle = modelshadow.tracked[t].map[l].thandle;
		glMakeTextureHandleNonResidentARB(handle);
		glDeleteTextures(1, &modelshadow.tracked[t].map[l].texture);
		modelshadow.tracked[t].map[l].framebuffer = 0;
		modelshadow.tracked[t].map[l].texture = 0;
	}

	//models
	models.queue_count = 0;
	glDeleteProgram(models.prog);
	models.prog = 0;

	for(unsigned i = 0; i < MAX_MODELS; ++i) {
		if(models.store[i].icount != 0) {
			glDeleteVertexArrays(1, &models.store[i].vao);
			glDeleteBuffers(1, &models.store[i].vbo);
			glDeleteBuffers(1, &models.store[i].ibo);
			models.store[i].icount = 0;
			models.store[i].vao = 0;
			models.store[i].vbo = 0;
			models.store[i].ibo = 0;
		}
	}

	//textures
	for(unsigned i = 0; i < MAX_TEXTURES; ++i) {
		if(textures.store[i].tex != 0) {
			glDeleteTextures(1, &textures.store[i].tex);
			textures.store[i].tex = 0;
		}
	}
}

static void set_light_uniforms(GLint screensize, GLint color, GLint dir, GLint trackedpos, GLint map)
{
	GLuint64 maps[MAX_LIGHTS*MAX_TRACKED_SHADOW];
	glUniform2i(screensize, (GLint) rendertarget.width, (GLint) rendertarget.height);
	if(trackedpos != -1) {
		for(GLint t = 0; t < MAX_TRACKED_SHADOW; ++t) {
			glUniform4fv(	trackedpos + t,	1, (const GLfloat*) modelshadow.tracked[t].pos.col[3].c);
		}
	}
	for(GLint l = 0; l < MAX_LIGHTS; ++l) {
		glUniform4fv(	color + l,		1, (const GLfloat*) lightdata[l].color.c);
		glUniform4fv(	dir + l,		1, (const GLfloat*) lightdata[l].dir.c);
		if(map != -1)
		{
			//point uniforms to textures
			for(GLint t = 0; t < MAX_TRACKED_SHADOW; ++t)
			{
				size_t idx = l * MAX_TRACKED_SHADOW + t;
				maps[idx] = modelshadow.tracked[t].map[l].thandle;
			}
		}
		glUniform2uiv(map, MAX_LIGHTS*MAX_TRACKED_SHADOW, (GLuint*)maps);
		GLERRCHECK
	}
}


static void frame_shadows(void)
{
	for(size_t t = 0; t < MAX_TRACKED_SHADOW; ++t)
	{
		uint32_t tid = modelshadow.tracked[t].id;
		if(tid == 0xFFFFFFFF) tid = 0;
		modelshadow.tracked[t].pos = yrVR_world_from_tracked(tid);

		for(size_t l = 0; l < MAX_LIGHTS; ++l)
		{
			//clear the shadowmap
			glBindFramebuffer(GL_FRAMEBUFFER, modelshadow.tracked[t].map[l].framebuffer);
			glViewport(0, 0, 1024, 1024);
			glClear(GL_DEPTH_BUFFER_BIT);
			//calc matrix
			mat4f* lmat = &modelshadow.tracked[t].map[l].mat;
			vec4f up = {1.0f,0.0f,0.0f,0.0f};
			lmat->col[2] = lightdata[l].dir;
			lmat->col[0] = vec3f_normalized(vec3f_cross(lightdata[l].dir, up));
			lmat->col[1] = vec3f_cross(lmat->col[0], lmat->col[2]);
			lmat->col[3] = vec4f_add(modelshadow.tracked[t].pos.col[3], vec4f_mul(-TRACKED_SHADOW_SIZE, lightdata[l].dir));
			*lmat = mat4f_invert(*lmat);
			//clumsy scale adjust but should be fine
			lmat->col[0] = vec4f_mul(-1.0f/TRACKED_SHADOW_SIZE, lmat->col[0]);
			lmat->col[1] = vec4f_mul(-1.0f/TRACKED_SHADOW_SIZE, lmat->col[1]);
			lmat->col[2] = vec4f_mul(-1.0f/TRACKED_SHADOW_SIZE, lmat->col[2]);
			lmat->col[3] = vec4f_mul(-1.0f/TRACKED_SHADOW_SIZE, lmat->col[3]);
			lmat->col[0].c[2] /= 2.0f;
			lmat->col[1].c[2] /= 2.0f;
			lmat->col[2].c[2] /= 2.0f;
			lmat->col[3].c[2] /= 2.0f;
			lmat->col[3].c[3] = 1.0f;
			//render shadowmap
			glUseProgram(modelshadow.prog);
			for(size_t i = 0; i < models.queue_count; ++i)
			{
				struct yr_rendertoken* token = models.queue + i;
				if(token->tracked_id == 0xFFFFFFFF) continue;
				if(token->tracked_id == modelshadow.tracked[t].id) {
					mat4f mat = mat4f_combine(modelshadow.tracked[t].pos, token->mat);
					glBindVertexArray(token->vao);
					glBindBuffer(GL_ARRAY_BUFFER, token->vbo);
					glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, token->ibo);
					glUniformMatrix4fv(modelshadow.uni_mdl_mat, 1, GL_FALSE, (const GLfloat*) &mat);
					glUniformMatrix4fv(modelshadow.uni_light_mat, 1, GL_FALSE, (const GLfloat*) &modelshadow.tracked[t].map[l].mat);
					glDrawElements(GL_TRIANGLES, token->icount, GL_UNSIGNED_SHORT, (GLvoid*) 0);
				}
			}
		}
	}
	GLERRCHECK
}

static void render_modeltex_frame(int lr)
{
	//prep context
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
	
	if(lr == 0) {
		frame_shadows();
	}

	//render models
	mat4f tracked_mat = {{
		{1.0f, 0.0f, 0.0f, 0.0f},
		{0.0f, 1.0f, 0.0f, 0.0f},
		{0.0f, 0.0f, 1.0f, 0.0f},
		{0.0f, 0.0f, 0.0f, 1.0f},
	}};
	uint32_t tracked_id = 0xFFFFFFFF;
	glUseProgram(models.prog);
	glUniform4iv(models.uni_cam_pos, 1, (const GLint*) camera.offset.c);

	set_light_uniforms(models.uni_light_screensize, models.uni_light_color, models.uni_light_dir, -1, -1);
	struct _yr_rt* rt = lr ? &rendertarget.left : &rendertarget.right;
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	glUniformMatrix4fv(models.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	glBindFramebuffer(GL_FRAMEBUFFER, rt->render_fb);
	glViewport(0, 0, rendertarget.width, rendertarget.height);

	for(size_t i = 0; i < models.queue_count; ++i)
	{
		struct yr_rendertoken* token = models.queue + i;

		yrColor clr = token->color;
		float clr_r = yrColor_red(clr);
		float clr_g = yrColor_green(clr);
		float clr_b = yrColor_blue(clr);
		float clr_a = yrColor_alpha(clr);
		mat4f mat = token->mat;
		vec4i pos = token->pos;
		if(token->tracked_id != 0xFFFFFFFF)
		{
			if(token->tracked_id != tracked_id) {
				tracked_id = token->tracked_id;
				tracked_mat = yrVR_world_from_tracked(tracked_id);
			}
			mat = mat4f_combine(tracked_mat, mat);
			pos = vec4i_add(yrVR_get_coord_offset(), token->pos);
		}

		glBindVertexArray(token->vao);
		glBindBuffer(GL_ARRAY_BUFFER, token->vbo);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, token->ibo);
		glBindTexture(GL_TEXTURE_2D, token->tex);
		glUniformMatrix4fv(models.uni_mdl_mat, 1, GL_FALSE, (const GLfloat*) &mat);
		glUniform4iv(models.uni_mdl_pos, 1, (const GLint*) pos.c);
		glUniform4f(models.uni_mdl_color, clr_r, clr_g, clr_b, clr_a);

		glDrawElements(GL_TRIANGLES, token->icount, GL_UNSIGNED_SHORT, (GLvoid*) 0);
	}

	//done, reset for next frame
	if(lr == 1) models.queue_count = 0;
	GLERRCHECK
}

yrTexture yrTexture_loadfile(const char* file)
{
	yrFile f = NULL;
	size_t rw;
	size_t len;
	void* data = NULL;

	f = yrFile_open(file, yrF_read);				if(!f) { yrLog(0, "Could not open texture file for reading", file); goto onerror; }
	len = (size_t) yrFile_seek(f, 0, yrF_seekend);
	yrFile_seek(f, 0, yrF_seekset);
	data = malloc(len);								if(!data) { yrLog(0, "Out of memory"); goto onerror; }
	rw = yrFile_read(f, (int64_t) len, data);		if(rw != len) { yrLog(0, "Read error"); goto onerror; }
	yrFile_close(f);

	yrTexture out = yrTexture_loadmem(len, data);	if(!out) goto onerror;

	free(data);
	return out;

onerror:
	yrLog(0, "Failed to load texture %s", file);
	free(data);
	if(f) yrFile_close(f);
	return NULL;
}

yrTexture yrTexture_loadmem(size_t len, void* texdata)
{
	struct yr_texture* tex = NULL;
	unsigned int width = 0;
	unsigned int height = 0;
	void* data = 0;

	//get texture object
	if(textures.nextfree == NULL) {
		yrLog(0, "Out of texture space, didn't load texture.");
	 	return NULL;
	}
	tex = textures.nextfree;
	textures.nextfree = textures.store + tex->next;
	tex->tex = 0;

	//read texture data
	const char* errtxt = yrImgLoad_decode(len, texdata, &width, &height, &data);
	if(errtxt) {
		yrLog(0, "Error while decoding texture: %s", errtxt);
		goto onerror;
	}

	//create texture & gen mip
	glGenTextures(1, &tex->tex);
	glBindTexture(GL_TEXTURE_2D, tex->tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
	GLint l = (GLint) ceilf(log2f((float)((width>height)?width:height)));
	glTexStorage2D(GL_TEXTURE_2D, l, GL_SRGB8_ALPHA8, width, height);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
	glGenerateMipmap(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during texture creation or loading: %x", glerr);
		goto onerror;
	}

	//done
	free(data);
	return tex;

onerror:
	yrLog(0, "Failed to load texture.");
	if(tex->tex) glDeleteTextures(1, &tex->tex);
	tex->tex = 0;
	free(data);
	textures.nextfree = tex;
	return NULL;
}

void yrTexture_unload(yrTexture tex)
{
	if(!tex) return;
	struct yr_texture* t = tex;
	glDeleteTextures(1, &t->tex);
	t->tex = 0;
	t->next = (unsigned int)(textures.nextfree - textures.store);
	textures.nextfree = tex;
}

yrModel yrModel_load(const char* file)
{
	struct yr_model* mdl = NULL;
	void* vtxdata = NULL;
	void* idxdata = NULL;
	uint32_t version = 0;
	uint16_t n_vtx = 0;
	uint16_t n_idx = 0;
	size_t rw = 0;
	yrFile f = NULL;

	//get model object
	if(models.nextfree == NULL) {
		yrLog(0, "Out of model space, didn't load %s", file);
	 	return NULL;
	}
	mdl = models.nextfree;
	models.nextfree = models.store + mdl->next;
	mdl->vao = 0;
	mdl->vbo = 0;
	mdl->ibo = 0;

	//read file version,nv,ni,data	
	f = yrFile_open(file, yrF_read);
	if(!f) { yrLog(0, "Could not open model file %s for reading", file); goto onerror; }
	rw = yrFile_read(f, 4, &version);									if(rw != 4) goto readerror;
	rw = yrFile_read(f, 2, &n_vtx);										if(rw != 2) goto readerror;
	rw = yrFile_read(f, 2, &n_idx);										if(rw != 2) goto readerror;
	if(version != 0) { yrLog(0, "Model %s is not compatible with this version of the software", file); goto onerror; } //TODO: model version definition
	vtxdata = malloc(n_vtx * sizeof(struct yr_modelvtx));				if(!vtxdata) { yrLog(0, "Not enough memory for the vertices of model %s", file); goto onerror; }
	idxdata = malloc(n_idx * sizeof(uint16_t));							if(!idxdata) { yrLog(0, "Not enough memory for the indices of model %s", file); goto onerror; }
	rw = yrFile_read(f, sizeof(struct yr_modelvtx) * n_vtx, vtxdata);	if(rw != sizeof(struct yr_modelvtx) * n_vtx) goto readerror;
	rw = yrFile_read(f, sizeof(uint16_t) * n_idx, idxdata);				if(rw != sizeof(uint16_t) * n_idx) goto readerror;
	yrFile_close(f);
	f = NULL;

	//create vbo & ibo
	glGenBuffers(1, &mdl->vbo);
	glBindBuffer(GL_ARRAY_BUFFER, mdl->vbo);
	glBufferData(GL_ARRAY_BUFFER, n_vtx * sizeof(struct yr_modelvtx), vtxdata, GL_STATIC_DRAW);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during model VBO creation: %x", glerr);
		yrLog(0, "Failed to load model %s", file);
		goto onerror;
	}

	glGenBuffers(1, &mdl->ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mdl->ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, n_idx * sizeof(uint16_t), idxdata, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during model IBO creation: %x", glerr);
		yrLog(0, "Failed to load model %s", file);
		goto onerror;
	}

	//create vao
	glGenVertexArrays(1, &mdl->vao);
	glBindVertexArray(mdl->vao);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(struct yr_modelvtx), (void*) 0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(struct yr_modelvtx), (void*) 12);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(struct yr_modelvtx), (void*) 24);
	glEnableVertexAttribArray(2);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during model VAO creation: %x", glerr);
		yrLog(0, "Failed to load model %s", file);
		goto onerror;
	}

	//done
	mdl->icount = n_idx;
	free(idxdata);
	free(vtxdata);
	return mdl;

readerror:
	yrLog(0, "%s is not a valid model file", file);
onerror:
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	if(mdl->vao) glDeleteVertexArrays(1, &mdl->vao);
	if(mdl->ibo) glDeleteBuffers(1, &mdl->ibo);
	if(mdl->vbo) glDeleteBuffers(1, &mdl->vbo);
	mdl->vao = 0;
	mdl->ibo = 0;
	mdl->vbo = 0;
	free(idxdata);
	free(vtxdata);
	if(f) yrFile_close(f);
	models.nextfree = mdl;
	return NULL;
}

void yrModel_unload(yrModel mdl)
{
	if(!mdl) return;
	struct yr_model* m = mdl;
	glDeleteBuffers(1, &m->ibo);
	glDeleteBuffers(1, &m->vbo);
	glDeleteVertexArrays(1, &m->vao);
	m->vao = 0;
	m->vbo = 0;
	m->ibo = 0;
	m->icount = 0;
	m->next = (uint32_t)(models.nextfree - models.store);
	models.nextfree = mdl;
}

void yrModel_render(yrModel mdl, yrTexture tex, const mat4f world_from_model, vec4i pos, yrColor clr, uint32_t tracked_id)
{
	if(models.queue_count == MAX_MODEL_RENDER) return;
	struct yr_rendertoken* token = models.queue + models.queue_count;
	models.queue_count += 1;

	token->mat = world_from_model;
	token->pos = pos;
	token->color = clr;
	token->tracked_id = tracked_id;
	token->vao		= ((struct yr_model*) mdl)->vao;
	token->vbo		= ((struct yr_model*) mdl)->vbo;
	token->ibo		= ((struct yr_model*) mdl)->ibo;
	token->icount	= ((struct yr_model*) mdl)->icount;
	token->tex		= ((struct yr_texture*) tex)->tex;
}

void yrRender_set_trackedshadow(unsigned idx, uint32_t tracked_id)
{
	YR_ASSERT(idx < MAX_TRACKED_SHADOW);
	modelshadow.tracked[idx].id = tracked_id;
}

/*****************
* Controller beams
******************/
static int render_beam_setup(void);
static void render_beam_frame(int lr);
static void render_beam_cleanup(void);

static struct
{
	struct {
		vec4f endnormal;
		vec4f start;
		vec4f endoff;
		yrColor color;
		int draw;
	}
	beam[MAX_BEAMS];

	GLuint vao;
	GLuint vbo;
	GLuint cap_tex;

	GLuint prog_beam;
	GLuint prog_cap;
	GLint uni_beam_cam_mat;
	GLint uni_beam_start;
	GLint uni_beam_endoff;
	GLint uni_beam_color;
	GLint uni_cap_cam_mat;
	GLint uni_cap_pos;
	GLint uni_cap_normal;
	GLint uni_cap_color;
}
beams = {0};

static int render_beam_setup(void)
{
	//beam geometry
	#define beam_sides 15
	vec4f beam_vtxs[2 * (beam_sides + 1)];
	for(size_t i = 0; i <= beam_sides; ++i)
	{
		float x = 0.002f * cosf(i * 2 * 3.1415926535897932f / beam_sides);
		float y = 0.002f * sinf(i * 2 * 3.1415926535897932f / beam_sides);

		beam_vtxs[i*2 + 0].x = x;
		beam_vtxs[i*2 + 0].y = y;
		beam_vtxs[i*2 + 0].z = 1.0f;
		beam_vtxs[i*2 + 0].w = 1.0f;
		beam_vtxs[i*2 + 1].x = x;
		beam_vtxs[i*2 + 1].y = y;
		beam_vtxs[i*2 + 1].z = 0.0f;
		beam_vtxs[i*2 + 1].w = 1.0f;
	}

	//beam vao + vbo
	glGenVertexArrays(1, &beams.vao);
	glGenBuffers(1, &beams.vbo);
	glBindVertexArray(beams.vao);
	glBindBuffer(GL_ARRAY_BUFFER, beams.vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(beam_vtxs), beam_vtxs, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 4, GL_FLOAT, FALSE, 0, NULL);
	glEnableVertexAttribArray(0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during beams VAO & VBO creation: %x", glerr);
		return -1;
	}

	//cap texture
	struct yr_texture* foo = yrTexture_loadfile("data/vr/beamcap.png");
	beams.cap_tex = foo ? foo->tex : 0;

	//beam shaderprog
	int err = 0;
	beams.prog_beam = glCreateProgram();
	err = OpenGL_shader(beams.prog_beam, GL_VERTEX_SHADER, VtxShader_beam);		if(err) { yrLog(0, "Compilation error(s) were in beam vertex shader."); return -1; }
	err = OpenGL_shader(beams.prog_beam, GL_FRAGMENT_SHADER, FragShader_beam);	if(err) { yrLog(0, "Compilation error(s) were in beam fragment shader."); return -1; }
	glLinkProgram(beams.prog_beam);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Beam shaderprogram failed to link: %x", glerr); return -1; }
	
	beams.uni_beam_cam_mat	= glGetUniformLocation(beams.prog_beam, "cam_mat");
	beams.uni_beam_start	= glGetUniformLocation(beams.prog_beam, "start");
	beams.uni_beam_endoff	= glGetUniformLocation(beams.prog_beam, "endoff");
	beams.uni_beam_color	= glGetUniformLocation(beams.prog_beam, "color");

	//beamcap shaderprog
	beams.prog_cap = glCreateProgram();
	err = OpenGL_shader(beams.prog_cap, GL_VERTEX_SHADER, VtxShader_beamcap);		if(err) { yrLog(0, "Compilation error(s) were in beamcap vertex shader."); return -1; }
	err = OpenGL_shader(beams.prog_cap, GL_FRAGMENT_SHADER, FragShader_beamcap);	if(err) { yrLog(0, "Compilation error(s) were in beamcap fragment shader."); return -1; }
	glLinkProgram(beams.prog_cap);
	glerr = glGetError();															if(glerr != GL_NO_ERROR) { yrLog(0, "Beamcap shaderprogram failed to link: %x", glerr); return -1; }

	beams.uni_cap_cam_mat	= glGetUniformLocation(beams.prog_cap, "cam_mat");
	beams.uni_cap_pos		= glGetUniformLocation(beams.prog_cap, "pos");
	beams.uni_cap_normal	= glGetUniformLocation(beams.prog_cap, "normal");
	beams.uni_cap_color		= glGetUniformLocation(beams.prog_cap, "color");

	return 0;
}

static void render_beam_cleanup(void)
{
	glUseProgram(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	glDeleteProgram(beams.prog_cap);
	glDeleteProgram(beams.prog_beam);
	glDeleteBuffers(1, &beams.vbo);
	glDeleteVertexArrays(1, &beams.vao);

	beams.prog_cap = 0;
	beams.prog_beam = 0;
	beams.cap_tex = 0; //cap texture will be cleaned up by modeltex_cleanup()
	beams.vbo = 0;
	beams.vao = 0;
	for(size_t i = 0; i < MAX_BEAMS; ++i)
		beams.beam[i].draw = 0;
}

static void render_beam_frame(int lr)
{
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	//beamcaps
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glUseProgram(beams.prog_cap);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindTexture(GL_TEXTURE_2D, beams.cap_tex);
	glUniformMatrix4fv(beams.uni_cap_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);

	for(size_t i = 0; i < MAX_BEAMS; ++i)
	{
		if(!beams.beam[i].draw) continue;
		vec4f pos = vec4f_add(beams.beam[i].start, beams.beam[i].endoff);
		yrColor clr = beams.beam[i].color;
		float clr_r = yrColor_red(clr);
		float clr_g = yrColor_green(clr);
		float clr_b = yrColor_blue(clr);
		float clr_a = yrColor_alpha(clr);

		glUniform4f(beams.uni_cap_color, clr_r, clr_g, clr_b, clr_a);
		glUniform4fv(beams.uni_cap_normal, 1, beams.beam[i].endnormal.c);
		glUniform4fv(beams.uni_cap_pos, 1, pos.c);

		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	}

	//beams themselves
	glUseProgram(beams.prog_beam);
	glBindVertexArray(beams.vao);
	glBindBuffer(GL_ARRAY_BUFFER, beams.vbo);
	glUniformMatrix4fv(beams.uni_beam_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);

	for(size_t i = 0; i < MAX_BEAMS; ++i)
	{
		if(!beams.beam[i].draw) continue;
		beams.beam[i].draw = 1 - lr; //reset for next frame
		yrColor clr = beams.beam[i].color;
		float clr_r = yrColor_red(clr);
		float clr_g = yrColor_green(clr);
		float clr_b = yrColor_blue(clr);
		float clr_a = yrColor_alpha(clr);

		glUniform4f(beams.uni_beam_color, clr_r, clr_g, clr_b, clr_a);
		glUniform4fv(beams.uni_beam_endoff, 1, beams.beam[i].endoff.c);
		glUniform4fv(beams.uni_beam_start, 1, beams.beam[i].start.c);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, 16*2);
	}
	GLERRCHECK
}

void yrRender_beam(unsigned idx, vec4f start, vec4f dir, vec4f endnormal, float dist, yrColor clr)
{
	YR_ASSERT(idx < MAX_BEAMS);
	beams.beam[idx].draw = 1;
	beams.beam[idx].start = start;
	beams.beam[idx].endoff = vec4f_mul(dist, dir);
	beams.beam[idx].endnormal = (vec3f_dot(dir, endnormal)>=0.0f) ? vec4f_neg(endnormal) : endnormal;
	beams.beam[idx].color = clr;
}

/*****************
* Teleport preview
******************/
static int render_teleport_setup(void);
static void render_teleport_frame(int lr);
static void render_teleport_cleanup(void);

static struct {
		vec4f arc_start;
		vec4f arc_end;
		vec4f arc_v;
		vec4f hmd_offset;
		float arc_max_t;
		float room_width;
		float room_height;
		int draw;

		GLuint vao;
		GLuint vbo;
		GLuint ibo;
		GLuint tex_room;
		GLuint tex_indicator;

		GLuint prog_arc;
		GLuint prog_floor;
		GLint uni_arc_cam_mat;
		GLint uni_arc_start;
		GLint uni_arc_initial_v;
		GLint uni_arc_maxt;
		GLint uni_floor_cam_mat;
		GLint uni_floor_dims;
}
teleport = {0};

static int render_teleport_setup(void)
{
	//room size
	ovrChaperone_GetPlayAreaSize(&teleport.room_width, &teleport.room_height);

	//vbo data
	#define segments 128
	#define subdiv 8
	size_t vbo_bufsize = segments * subdiv * 3 * sizeof(float);
	float* vbo_data = (float*) malloc(vbo_bufsize);
	if(!vbo_data) {
		yrLog(0, "Out of memory");
		return -1;
	}

	for(size_t seg = 0; seg < segments; ++seg)
	for(size_t sub = 0; sub < subdiv; ++sub)
	{
		float ang = (((float)sub/subdiv)*2*3.1415926535897932f);
		vbo_data[seg*subdiv*3 + sub*3 + 0] = 0.004f * cosf(ang);
		vbo_data[seg*subdiv*3 + sub*3 + 1] = 0.004f * sinf(ang);
		vbo_data[seg*subdiv*3 + sub*3 + 2] = (float) seg / (segments-1);
	}

	//ibo data
	size_t ibo_bufsize = (segments-1) * subdiv * 6 * sizeof(uint16_t);
	uint16_t* ibo_data = (uint16_t*) malloc(ibo_bufsize);
	if(!ibo_data) {
		free(vbo_data);
		yrLog(0, "Out of memory");
		return -1;
	}

	for(size_t seg = 0; seg < segments - 1; ++seg)
	for(size_t sub = 0; sub < subdiv; ++sub)
	{
		uint16_t base = (uint16_t)(seg * subdiv);
		ibo_data[seg*subdiv*6 + sub*6 + 0] = base + (uint16_t)sub;
		ibo_data[seg*subdiv*6 + sub*6 + 1] = base + ((uint16_t)sub + 1) % subdiv;
		ibo_data[seg*subdiv*6 + sub*6 + 2] = base + subdiv + (uint16_t)sub;

		ibo_data[seg*subdiv*6 + sub*6 + 3] = base + (uint16_t)sub;
		ibo_data[seg*subdiv*6 + sub*6 + 4] = base + subdiv + (uint16_t)sub;
		ibo_data[seg*subdiv*6 + sub*6 + 5] = base + subdiv + ((uint16_t)sub + subdiv - 1) % subdiv;
	}

	//vao + vbo + ibo
	glGenVertexArrays(1, &teleport.vao);
	glGenBuffers(1, &teleport.vbo);
	glGenBuffers(1, &teleport.ibo);
	glBindVertexArray(teleport.vao);
	glBindBuffer(GL_ARRAY_BUFFER, teleport.vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, teleport.ibo);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);
	glBufferData(GL_ARRAY_BUFFER, vbo_bufsize, vbo_data, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo_bufsize, ibo_data, GL_STATIC_DRAW);
	free(vbo_data);
	free(ibo_data);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during teleport VAO, VBO & IBO creation: %x", glerr);
		return -1;
	}

	//textures
	struct yr_texture* foo = yrTexture_loadfile("data/vr/floor.png");
	teleport.tex_room = foo ? foo->tex : 0;
	struct yr_texture* bar = yrTexture_loadfile("data/vr/hmdindic.png");
	teleport.tex_indicator = bar ? bar->tex : 0;

	//arc shaderprog
	int err = 0;
	teleport.prog_arc = glCreateProgram();
	err = OpenGL_shader(teleport.prog_arc, GL_VERTEX_SHADER, VtxShader_tparc);		if(err) { yrLog(0, "Compilation error(s) were in teleport arc vertex shader."); return -1; }
	err = OpenGL_shader(teleport.prog_arc, GL_FRAGMENT_SHADER, FragShader_tparc);	if(err) { yrLog(0, "Compilation error(s) were in teleport arc fragment shader."); return -1; }
	glLinkProgram(teleport.prog_arc);
	glerr = glGetError();															if(glerr != GL_NO_ERROR) { yrLog(0, "Teleport arc shaderprogram failed to link: %x", glerr); return -1; }

	teleport.uni_arc_cam_mat	= glGetUniformLocation(teleport.prog_arc, "cam_mat");
	teleport.uni_arc_start		= glGetUniformLocation(teleport.prog_arc, "start");
	teleport.uni_arc_initial_v	= glGetUniformLocation(teleport.prog_arc, "initial_v");
	teleport.uni_arc_maxt		= glGetUniformLocation(teleport.prog_arc, "max_t");

	//floor shaderprog
	teleport.prog_floor = glCreateProgram();
	err = OpenGL_shader(teleport.prog_floor, GL_VERTEX_SHADER, VtxShader_tpfloor);		if(err) { yrLog(0, "Compilation error(s) were in teleport floor vertex shader."); return -1; }
	err = OpenGL_shader(teleport.prog_floor, GL_FRAGMENT_SHADER, FragShader_tpfloor);	if(err) { yrLog(0, "Compilation error(s) were in teleport floor fragment shader."); return -1; }
	glLinkProgram(teleport.prog_floor);
	glerr = glGetError();																if(glerr != GL_NO_ERROR) { yrLog(0, "Teleport floor shaderprogram failed to link: %x", glerr); return -1; }

	teleport.uni_floor_cam_mat	= glGetUniformLocation(teleport.prog_floor, "cam_mat");
	teleport.uni_floor_dims		= glGetUniformLocation(teleport.prog_floor, "dims");
	return 0;
}

static void render_teleport_cleanup(void)
{
	glUseProgram(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	glDeleteProgram(teleport.prog_floor);
	glDeleteProgram(teleport.prog_arc);

	glDeleteBuffers(1, &teleport.ibo);
	glDeleteBuffers(1, &teleport.vbo);
	glDeleteVertexArrays(1, &teleport.vao);

	teleport.prog_floor = 0;
	teleport.prog_arc = 0;
	teleport.tex_indicator = 0; //textures are cleaned up in modeltex_cleanup
	teleport.tex_room = 0;
	teleport.ibo = 0;
	teleport.vbo = 0;
	teleport.vao = 0;
	teleport.draw = 0;
}

static void render_teleport_frame(int lr)
{
	if(!teleport.draw) return;
	teleport.draw = 1 - lr; //reset for next frame
	
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	glEnable(GL_BLEND);
	glUseProgram(teleport.prog_floor);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glUniformMatrix4fv(teleport.uni_floor_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	float dims[2][2];
	if(teleport.arc_max_t > 0.0f)
	{
		//render room view
		dims[0][0] = teleport.arc_end.x - teleport.hmd_offset.x - teleport.room_width/2;
		dims[0][1] = teleport.arc_end.y - teleport.hmd_offset.y - teleport.room_height/2;
		dims[1][0] = dims[0][0] + teleport.room_width;
		dims[1][1] = dims[0][1] + teleport.room_height;

		glEnable(GL_DEPTH_TEST);
		glBindTexture(GL_TEXTURE_2D, teleport.tex_room);
		glUniform2fv(teleport.uni_floor_dims, 2, (GLfloat*) dims);

		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
		
		//render hmd indicator
		dims[0][0] = teleport.arc_end.x - 0.25f;
		dims[0][1] = teleport.arc_end.y - 0.25f;
		dims[1][0] = dims[0][0] + 0.5f;
		dims[1][1] = dims[0][1] + 0.5f;

		glDisable(GL_DEPTH_TEST);
		glBindTexture(GL_TEXTURE_2D, teleport.tex_indicator);
		glUniform2fv(teleport.uni_floor_dims, 2, (GLfloat*) dims);

		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	}
	//render arc
	glEnable(GL_DEPTH_TEST);
	glUseProgram(teleport.prog_arc);
	glBindVertexArray(teleport.vao);
	glBindBuffer(GL_ARRAY_BUFFER, teleport.vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, teleport.ibo);

	glUniform4fv(teleport.uni_arc_start, 1, teleport.arc_start.c);
	glUniform4fv(teleport.uni_arc_initial_v, 1, teleport.arc_v.c);
	glUniform1f(teleport.uni_arc_maxt, teleport.arc_max_t);

	glUniformMatrix4fv(teleport.uni_arc_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	glDrawElements(GL_TRIANGLES, (segments-1)*subdiv*6, GL_UNSIGNED_SHORT, (GLvoid*) 0);

	GLERRCHECK
}

void yrRender_teleport(vec4f start, vec4f initial_v, float max_t, vec4f hmd_off)
{
	teleport.draw = 1;
	teleport.arc_start = start;
	teleport.arc_v = initial_v;
	teleport.arc_max_t = max_t;
	teleport.arc_end = vec4f_add(start, vec4f_mul(max_t, initial_v));
	teleport.arc_end.z = 0;
	teleport.hmd_offset = hmd_off;
}

/**************
* Quad overlays
***************/
static int render_overlay_setup(void);
static void render_overlay_frame(int lr);
static void render_overlay_cleanup(void);

static struct {
	struct {
		vec4f normal;
		GLuint fb;
		GLuint tex;
		int draw;
		float lit;
	}
	overlay[MAX_OVERLAYS];

	struct  {
		GLuint vao;
		GLuint vbo;	
		GLuint prog;
		GLint uni_cam_mat;
		GLint uni_cam_pos;
		GLint uni_quad_normal;
		GLint uni_lit;
		GLint uni_light_screensize;
		GLint uni_light_dir;
		GLint uni_light_color;
		GLint uni_light_trackedpos;
		GLint uni_light_map;
	}
	render;

	struct {
		GLuint fb_apply;
		GLuint prog;
		GLint uni_pos;
		GLint uni_color;
	}
	draw;

	struct {
		GLuint vao;
		GLuint vbo;
		GLuint ibo;
		GLuint prog;
		GLint uni_depth;
		GLint uni_color;
	}
	erase;
}
overlays = {0};

static int render_overlay_setup(void)
{
	//overlay rendertargets
	for(size_t i = 0; i < MAX_OVERLAYS; ++i)
	{
		glGenFramebuffers(1, &overlays.overlay[i].fb);
		glGenTextures(1, &overlays.overlay[i].tex);
		glBindFramebuffer(GL_FRAMEBUFFER, overlays.overlay[i].fb);
		glBindTexture(GL_TEXTURE_2D, overlays.overlay[i].tex);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
		glTexStorage2D(GL_TEXTURE_2D, 12, GL_SRGB8_ALPHA8, 2048, 2048);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, overlays.overlay[i].tex, 0);

		if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
		else {
			yrLog(0, "OpenGL error while creating overlay framebuffer: %x", glGetError());
			return -1;
		}
	}
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//render vao + vbo
	glGenVertexArrays(1, &overlays.render.vao);
	glGenBuffers(1, &overlays.render.vbo);
	glBindVertexArray(overlays.render.vao);
	glBindBuffer(GL_ARRAY_BUFFER, overlays.render.vbo);
	glBufferData(GL_ARRAY_BUFFER, MAX_OVERLAYS * 4 * 5 * 4, NULL, GL_DYNAMIC_DRAW);
	glVertexAttribIPointer(0, 3, GL_INT, 5*4, (GLvoid*) 0);
	glVertexAttribPointer(1, 2, GL_FLOAT, FALSE, 5*4, (GLvoid*) (3*4));
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during overlay VAO & VBO creation: %x", glerr);
		return -1;
	}

	//render prog
	int err = 0;
	overlays.render.prog = glCreateProgram();
	err = OpenGL_shader(overlays.render.prog, GL_VERTEX_SHADER, VtxShader_overlay_render);		if(err) { yrLog(0, "Compilation error(s) were in overlay render vertex shader."); return -1; }
	err = OpenGL_shader(overlays.render.prog, GL_FRAGMENT_SHADER, FragShader_overlay_render);	if(err) { yrLog(0, "Compilation error(s) were in overlay render fragment shader."); return -1; }
	glLinkProgram(overlays.render.prog);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Overlay render shaderprogram failed to link: %x", glerr); return -1; }

	overlays.render.uni_cam_mat		= glGetUniformLocation(overlays.render.prog, "cam_mat");
	overlays.render.uni_cam_pos		= glGetUniformLocation(overlays.render.prog, "cam_pos");
	overlays.render.uni_quad_normal	= glGetUniformLocation(overlays.render.prog, "quad_normal");
	overlays.render.uni_lit			= glGetUniformLocation(overlays.render.prog, "lit");
	overlays.render.uni_light_screensize	= glGetUniformLocation(overlays.render.prog, "light_screensize");
	overlays.render.uni_light_dir			= glGetUniformLocation(overlays.render.prog, "light_dir");
	overlays.render.uni_light_color			= glGetUniformLocation(overlays.render.prog, "light_color");
	overlays.render.uni_light_trackedpos	= glGetUniformLocation(overlays.render.prog, "light_trackedpos");
	overlays.render.uni_light_map			= glGetUniformLocation(overlays.render.prog, "light_map");

	//draw framebuffer
	glGenFramebuffers(1, &overlays.draw.fb_apply);

	//draw prog
	overlays.draw.prog = glCreateProgram();
	err = OpenGL_shader(overlays.draw.prog, GL_VERTEX_SHADER, VtxShader_overlay_draw);		if(err) { yrLog(0, "Compilation error(s) were in overlay draw vertex shader."); return -1; }
	err = OpenGL_shader(overlays.draw.prog, GL_FRAGMENT_SHADER, FragShader_overlay_draw);	if(err) { yrLog(0, "Compilation error(s) were in overlay draw fragment shader."); return -1; }
	glLinkProgram(overlays.draw.prog);
	glerr = glGetError();													if(glerr != GL_NO_ERROR) { yrLog(0, "Overlay draw shaderprogram failed to link: %x", glerr); return -1; }

	overlays.draw.uni_pos	= glGetUniformLocation(overlays.draw.prog, "pos");
	overlays.draw.uni_color	= glGetUniformLocation(overlays.draw.prog, "color");

	//erase vao + vbo + ibo
	static const uint16_t erase_ibo_data[12*3] = { 0,1,2,  0,2,3,  0,1,5,  0,5,4,  1,2,6,  1,6,5,  2,3,7,  2,7,6,  3,0,4,  3,4,7,  4,5,6,  4,6,7 };
	glGenVertexArrays(1, &overlays.erase.vao);
	glGenBuffers(1, &overlays.erase.vbo);
	glGenBuffers(1, &overlays.erase.ibo);
	glBindVertexArray(overlays.erase.vao);
	glBindBuffer(GL_ARRAY_BUFFER, overlays.erase.vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, overlays.erase.ibo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 12 * 3 * sizeof(uint16_t), erase_ibo_data, GL_STATIC_DRAW);
	glBufferData(GL_ARRAY_BUFFER, 8 * 4 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
	glVertexAttribPointer(0, 4, GL_FLOAT, FALSE, 4 * sizeof(float), (GLvoid*) 0);
	glEnableVertexAttribArray(0);
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during erase VAO, VBO & IBO creation: %x", glerr);
		return -1;
	}

	//erase prog
	overlays.erase.prog = glCreateProgram();
	err = OpenGL_shader(overlays.erase.prog, GL_VERTEX_SHADER, VtxShader_overlay_erase);	if(err) { yrLog(0, "Compilation error(s) were in erase vertex shader."); return -1; }
	err = OpenGL_shader(overlays.erase.prog, GL_FRAGMENT_SHADER, FragShader_overlay_erase);	if(err) { yrLog(0, "Compilation error(s) were in erase fragment shader."); return -1; }
	glLinkProgram(overlays.erase.prog);
	glerr = glGetError();													if(glerr != GL_NO_ERROR) { yrLog(0, "Erase shaderprogram failed to link: %x", glerr); return -1; }

	overlays.erase.uni_depth	= glGetUniformLocation(overlays.erase.prog, "depth");
	overlays.erase.uni_color	= glGetUniformLocation(overlays.erase.prog, "color");
	return 0;
}

static void render_overlay_cleanup(void)
{
	glUseProgram(0);
	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//erase stuff
	glDeleteProgram(overlays.erase.prog);
	glDeleteBuffers(1, &overlays.erase.ibo);
	glDeleteBuffers(1, &overlays.erase.vbo);
	glDeleteVertexArrays(1, &overlays.erase.vao);
	overlays.erase.prog = 0;
	overlays.erase.ibo = 0;
	overlays.erase.vbo = 0;
	overlays.erase.vao = 0;

	//draw stuff
	glDeleteProgram(overlays.draw.prog);
	glDeleteFramebuffers(1, &overlays.draw.fb_apply);
	overlays.draw.prog = 0;
	overlays.draw.fb_apply = 0;

	//render stuff
	glDeleteProgram(overlays.render.prog);
	glDeleteBuffers(1, &overlays.render.vbo);
	glDeleteVertexArrays(1, &overlays.render.vao);
	overlays.render.prog = 0;
	overlays.render.vbo = 0;
	overlays.render.vao = 0;

	//overlay rendertargets
	for(size_t i = 0; i < MAX_OVERLAYS; ++i)
	{
		glDeleteFramebuffers(1, &overlays.overlay[i].fb);
		glDeleteTextures(1, &overlays.overlay[i].tex);
		overlays.overlay[i].fb = 0;
		overlays.overlay[i].tex = 0;
		overlays.overlay[i].draw = 0;
	}
}

static void render_overlay_frame(int lr)
{
	glEnable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);
	glUseProgram(overlays.render.prog);
	glBindVertexArray(overlays.render.vao);
	glBindBuffer(GL_ARRAY_BUFFER, overlays.render.vbo);

	glUniform4iv(overlays.render.uni_cam_pos, 1, (const GLint*) camera.offset.c);

	set_light_uniforms(overlays.render.uni_light_screensize,
					   overlays.render.uni_light_color,
					   overlays.render.uni_light_dir,
					   overlays.render.uni_light_trackedpos,
					   overlays.render.uni_light_map);

	GLERRCHECK

	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	glUniformMatrix4fv(overlays.render.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
	for(size_t i = 0; i < MAX_OVERLAYS; ++i)
	{
		if(!overlays.overlay[i].draw) continue;
		overlays.overlay[i].draw = 1 - lr;

		glBindTexture(GL_TEXTURE_2D, overlays.overlay[i].tex);
		glUniform1f(overlays.render.uni_lit, overlays.overlay[i].lit);
		glUniform3fv(overlays.render.uni_quad_normal, 1, overlays.overlay[i].normal.c);

		glDrawArrays(GL_TRIANGLE_FAN, (GLint) i*4, 4);
	}
	GLERRCHECK
}

void yrRenderOverlay_clear(unsigned idx)
{
	YR_ASSERT(idx < MAX_OVERLAYS);
	glBindFramebuffer(GL_FRAMEBUFFER, overlays.overlay[idx].fb);
	glViewport(0, 0, 2048, 2048);

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
}

void yrRenderOverlay_draw(unsigned idx, yrTexture tex, float x0, float y0, float x1, float y1, yrColor clr)
{
	if(!tex) return;
	YR_ASSERT(idx < MAX_OVERLAYS);
	glBindFramebuffer(GL_FRAMEBUFFER, overlays.overlay[idx].fb);
	glViewport(0, 0, 2048, 2048);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);

	float clr_r = yrColor_red(clr);
	float clr_g = yrColor_green(clr);
	float clr_b = yrColor_blue(clr);
	float clr_a = yrColor_alpha(clr);

	glUseProgram(overlays.draw.prog);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)tex)->tex);
	glUniform4f(overlays.draw.uni_pos, x0, y0, x1, y1);
	glUniform4f(overlays.draw.uni_color, clr_r, clr_g, clr_b, clr_a);

	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	GLERRCHECK
}

void yrRenderOverlay_mask_erase(unsigned idx, float depth, vec4f* v, yrColor bgcolor)
{
	//v contains the 8 corners of the erase volume in quad space
	YR_ASSERT(idx < MAX_OVERLAYS);
	glBindFramebuffer(GL_FRAMEBUFFER, overlays.overlay[idx].fb);
	glViewport(0, 0, 2048, 2048);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);

	glUseProgram(overlays.erase.prog);
	glBindVertexArray(overlays.erase.vao);
	glBindBuffer(GL_ARRAY_BUFFER, overlays.erase.vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, overlays.erase.ibo);
	glUniform1f(overlays.erase.uni_depth, depth);
	glUniform3f(overlays.erase.uni_color, yrColor_red(bgcolor), yrColor_green(bgcolor), yrColor_blue(bgcolor));

	vec4f* buf = (vec4f*) glMapBufferRange(GL_ARRAY_BUFFER, 0, 8*4*sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
	memcpy(buf, v, 8*4*sizeof(float));
	glUnmapBuffer(GL_ARRAY_BUFFER);

	glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_SHORT, 0);
	GLERRCHECK
}

void yrRenderOverlay_apply(unsigned idx, yrQuad* q, uint64_t changed, int erase)
{
	YR_ASSERT(idx < MAX_OVERLAYS);
	YR_ASSERT(q);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glEnable(GL_FRAMEBUFFER_SRGB);
	if(erase) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);

	glBindTexture(GL_TEXTURE_2D, overlays.overlay[idx].tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 10);
	glGenerateMipmap(GL_TEXTURE_2D);

	q->flags |= QUAD_CLEARCACHE;
	for(GLint i = 0; (unsigned) i < q->ext->tile_max_idx; ++i, changed >>= 1)
	{
		if(!(changed & 1)) continue;
		//copy or create new tile
		uint32_t tile = q->ext->tiles[i];
		GLuint texfull = 0;
		GLuint texmips = 0;
		TileID tid = {tile};
		tile = (tile == 0xFFFFFFFFul) ?  yrTileStore_new(&texfull, &texmips).t : yrTileStore_copy(tid ,&texfull, &texmips).t;
		q->ext->tiles[i] = tile;
		//update tile
		if(!texfull) {
			yrLog(1, "Failed to aqcuire an editable tile (%x->%x)", q->ext->tiles[i], tile);
			continue;
		}
		for(int full = 1; full >= 0; full -= 1)
		{
			GLint lvlbase = full ? 0 : 2;
			GLuint tex = full ? texfull : texmips;

			for(GLint lvl = lvlbase; lvl < 9; ++lvl) {
				glBindFramebuffer(GL_FRAMEBUFFER, overlays.draw.fb_apply);
				glBindTexture(GL_TEXTURE_2D, texfull);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, lvl - lvlbase);
				//copy
				glViewport(0, 0, 256 >> lvl, 256 >> lvl);
				glUseProgram(overlays.draw.prog);
				glBindVertexArray(unitquad.vao);
				glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
				glBindTexture(GL_TEXTURE_2D, overlays.overlay[idx].tex);

				//we are reusing the "draw" shaderprog here, but an overlay contains 8 times as many tiles in both dimensions
				//so all coordinates need to be multiplied by 8
				float xoff = -(float)(i % q->tile_width) * 8;
				float yoff = -(float)(i / q->tile_width) * 8;
				glUniform4f(overlays.draw.uni_pos, xoff, yoff, xoff + 64.0f, yoff + 64.0f);
				glUniform4f(overlays.draw.uni_color, 1.0f, 1.0f, 1.0f, 1.0f);

				glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
			}
		}
		tid.t = tile;
		yrTileStore_finishtile(tid);
		GLERRCHECK;
	}
	//restore premultiplied alpha blending and mips
	if(erase) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glBindTexture(GL_TEXTURE_2D, overlays.overlay[idx].tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
	//done
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_FRAMEBUFFER_SRGB);
}

void yrRenderOverlay_render(unsigned idx, yrQuad* q, int lit)
{
	YR_ASSERT(idx < MAX_OVERLAYS);
	glBindBuffer(GL_ARRAY_BUFFER, overlays.render.vbo);
	size_t stride = 4 * 5 * 4;
	int32_t* buf = (int32_t*) glMapBufferRange(GL_ARRAY_BUFFER, idx * stride, stride, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);

	buf[0] = q->v[0].x;
	buf[1] = q->v[0].y;
	buf[2] = q->v[0].z;
	*(float*) (buf + 3) = q->uv_draw[0];
	*(float*) (buf + 4) = q->uv_draw[1];

	buf[5] = q->v[1].x;
	buf[6] = q->v[1].y;
	buf[7] = q->v[1].z;
	*(float*) (buf + 8) = q->uv_draw[0] + q->width * UV_PER_METER;
	*(float*) (buf + 9) = q->uv_draw[1];

	vec4i v12 = vec4i_sub(vec4i_add(q->v[1], q->v[2]), q->v[0]);
	buf[10] = v12.x;
	buf[11] = v12.y;
	buf[12] = v12.z;
	*(float*) (buf + 13) = q->uv_draw[0] + q->width * UV_PER_METER;
	*(float*) (buf + 14) = q->uv_draw[1] + q->height * UV_PER_METER;

	buf[15] = q->v[2].x;
	buf[16] = q->v[2].y;
	buf[17] = q->v[2].z;
	*(float*) (buf + 18) = q->uv_draw[0];
	*(float*) (buf + 19) = q->uv_draw[1] + q->height * UV_PER_METER;

	glUnmapBuffer(GL_ARRAY_BUFFER);
	GLERRCHECK;

	overlays.overlay[idx].draw = 1;
	overlays.overlay[idx].lit = lit ? 1.0f : 0.0f;
	overlays.overlay[idx].normal = vec3f_normalized(vec3f_cross(vec4f_from_vec4i(vec4i_sub(q->v[1],
																						   q->v[0])),
																vec4f_from_vec4i(vec4i_sub(q->v[2],
																						   q->v[0]))));
}

/****************
* Tracked overlay
*****************/
static struct {
	struct {
		uint32_t id;
		GLuint fb;
		GLuint tex;
		int draw;
	}
	overlay[MAX_TRACKED_OVERLAY];

	struct  {
		GLuint prog;
		GLint uni_cam_mat;
		GLint uni_tracked_mat;
	}
	render;

	struct {
		GLuint prog;
		GLint uni_pos;
		GLint uni_color;
	}
	draw;
}
trackedoverlay = {0};

static int render_trackedoverlay_setup(void);
static void render_trackedoverlay_frame(int lr);
static void render_trackedoverlay_cleanup(void);

static int render_trackedoverlay_setup(void)
{
	//overlay rendertargets
	for(size_t i = 0; i < MAX_TRACKED_OVERLAY; ++i)
	{
		trackedoverlay.overlay[i].id = 0xfffffffful;
		trackedoverlay.overlay[i].draw = 0;
		glGenFramebuffers(1, &trackedoverlay.overlay[i].fb);
		glGenTextures(1, &trackedoverlay.overlay[i].tex);
		glBindFramebuffer(GL_FRAMEBUFFER, trackedoverlay.overlay[i].fb);
		glBindTexture(GL_TEXTURE_2D, trackedoverlay.overlay[i].tex);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
		glTexStorage2D(GL_TEXTURE_2D, 8, GL_SRGB8_ALPHA8, 128, 128);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, trackedoverlay.overlay[i].tex, 0);

		if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
		else {
			yrLog(0, "OpenGL error while creating tracked overlay framebuffer: %x", glGetError());
			return -1;
		}
	}
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//render prog
	int err = 0;
	trackedoverlay.render.prog = glCreateProgram();
	err = OpenGL_shader(trackedoverlay.render.prog, GL_VERTEX_SHADER, VtxShader_trackedoverlay_render);
	if(err) { yrLog(0, "Compilation error(s) were in tracked overlay render vertex shader."); return -1; }
	err = OpenGL_shader(trackedoverlay.render.prog, GL_FRAGMENT_SHADER, FragShader_trackedoverlay_render);
	if(err) { yrLog(0, "Compilation error(s) were in tracked overlay render fragment shader."); return -1; }
	glLinkProgram(trackedoverlay.render.prog);
	GLuint glerr = glGetError();
	if(glerr != GL_NO_ERROR) { yrLog(0, "Tracked overlay render shaderprogram failed to link: %x", glerr); return -1; }

	trackedoverlay.render.uni_cam_mat		= glGetUniformLocation(trackedoverlay.render.prog, "cam_mat");
	trackedoverlay.render.uni_tracked_mat	= glGetUniformLocation(trackedoverlay.render.prog, "tracked_mat");
	
	//draw prog
	trackedoverlay.draw.prog = glCreateProgram();
	err = OpenGL_shader(trackedoverlay.draw.prog, GL_VERTEX_SHADER, VtxShader_trackedoverlay_draw);		
	if(err) { yrLog(0, "Compilation error(s) were in tracked overlay draw vertex shader."); return -1; }
	err = OpenGL_shader(trackedoverlay.draw.prog, GL_FRAGMENT_SHADER, FragShader_overlay_draw);	
	if(err) { yrLog(0, "Compilation error(s) were in tracked overlay draw fragment shader."); return -1; }
	glLinkProgram(trackedoverlay.draw.prog);
	glerr = glGetError();													
	if(glerr != GL_NO_ERROR) { yrLog(0, "Tracked overlay draw shaderprogram failed to link: %x", glerr); return -1; }

	trackedoverlay.draw.uni_pos		= glGetUniformLocation(trackedoverlay.draw.prog, "pos");
	trackedoverlay.draw.uni_color	= glGetUniformLocation(trackedoverlay.draw.prog, "color");

	return 0;
}

static void render_trackedoverlay_cleanup(void)
{
	glUseProgram(0);
	glBindVertexArray(0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//draw stuff
	glDeleteProgram(trackedoverlay.draw.prog);
	trackedoverlay.draw.prog = 0;

	//render stuff
	glDeleteProgram(trackedoverlay.render.prog);
	trackedoverlay.render.prog = 0;

	//overlay rendertargets
	for(size_t i = 0; i < MAX_TRACKED_OVERLAY; ++i)
	{
		glDeleteFramebuffers(1, &trackedoverlay.overlay[i].fb);
		glDeleteTextures(1, &trackedoverlay.overlay[i].tex);
		trackedoverlay.overlay[i].fb = 0;
		trackedoverlay.overlay[i].tex = 0;
		trackedoverlay.overlay[i].draw = 0;
	}
}

static void render_trackedoverlay_frame(int lr)
{
	glEnable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);
	glUseProgram(trackedoverlay.render.prog);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;
	glUniformMatrix4fv(trackedoverlay.render.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);

	for(size_t i = 0; i < MAX_TRACKED_OVERLAY; ++i)
	{
		if(!trackedoverlay.overlay[i].draw) continue;
		trackedoverlay.overlay[i].draw = 1 - lr;
		mat4f trackedmat = yrVR_world_from_tracked(trackedoverlay.overlay[i].id);

		glUniformMatrix4fv(trackedoverlay.render.uni_tracked_mat, 1, GL_FALSE, (const GLfloat*) &trackedmat);
		glBindTexture(GL_TEXTURE_2D, trackedoverlay.overlay[i].tex);

		glDrawArrays(GL_TRIANGLE_FAN, (GLint) 0, 4);
	}
	GLERRCHECK;
}

void yrRenderTrackedOverlay_clear(unsigned idx)
{
	YR_ASSERT(idx < MAX_TRACKED_OVERLAY);
	glBindFramebuffer(GL_FRAMEBUFFER, trackedoverlay.overlay[idx].fb);
	glViewport(0, 0, 128, 128);

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
}

void yrRenderTrackedOverlay_draw(unsigned idx, yrTexture tex, float x0, float y0, float x1, float y1, yrColor clr)
{
	YR_ASSERT(idx < MAX_TRACKED_OVERLAY);
	glBindFramebuffer(GL_FRAMEBUFFER, trackedoverlay.overlay[idx].fb);
	glViewport(0, 0, 128, 128);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);

	float clr_r = yrColor_red(clr);
	float clr_g = yrColor_green(clr);
	float clr_b = yrColor_blue(clr);
	float clr_a = yrColor_alpha(clr);

	glUseProgram(trackedoverlay.draw.prog);
	glBindVertexArray(unitquad.vao);
	glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
	glBindTexture(GL_TEXTURE_2D, ((struct yr_texture*)tex)->tex);
	glUniform4f(trackedoverlay.draw.uni_pos, x0, y0, x1, y1);
	glUniform4f(trackedoverlay.draw.uni_color, clr_r, clr_g, clr_b, clr_a);

	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	GLERRCHECK;
}

void yrRenderTrackedOverlay_render(unsigned idx)
{
	YR_ASSERT(idx < MAX_OVERLAYS);
	trackedoverlay.overlay[idx].draw = 1;
}

void yrRenderTrackedOverlay_set(unsigned idx, uint32_t tracked_id)
{
	YR_ASSERT(idx < MAX_TRACKED_OVERLAY);
	trackedoverlay.overlay[idx].id = tracked_id;
}

/***************
* Bar statistics
****************/
static struct {
	float value[bs_end];
	yrColor color[bs_end];
	float display_value[bs_end];
	int row[bs_end];

	GLuint prog;
	GLint uni_cam_mat;
	GLint uni_value;
	GLint uni_color;
	GLint uni_row;
}
barstat = {0};

static int render_barstat_setup(void);
static void render_barstat_frame(int lr);
static void render_barstat_cleanup(void);

static int render_barstat_setup(void)
{
	memset(&barstat, 0, sizeof(barstat));
	barstat.color[bsTimeQuads] = 0xFFFF8080ul;
	barstat.color[bsTimeRender] = 0xFF80C0FFul;
	barstat.color[bsTimeInteraction] = 0xFF80FF80ul;
	barstat.color[bsTimeOther] = 0xFF8080FFul;
	barstat.color[bsBlankReserve] = 0xFF000080;
	barstat.color[bsImporantQueue] = 0xFF008000;

	barstat.row[bsTimeQuads] = 0;
	barstat.row[bsTimeRender] = 0;
	barstat.row[bsTimeInteraction] = 0;
	barstat.row[bsTimeOther] = 0;
	barstat.row[bsBlankReserve] = 1;
	barstat.row[bsImporantQueue] = 2;
	barstat.row[bsFullAvailable] = 3;
	barstat.row[bsMipsAvailable] = 3;
	barstat.row[bsIdxAvailable] = 3;
	
	//create barstat program
	barstat.prog = glCreateProgram();
	int err = 0;
	GLuint glerr = 0;
	err = OpenGL_shader(barstat.prog, GL_VERTEX_SHADER, VtxShader_barstat);		if(err) { yrLog(0, "Compilation error(s) were in barstat vertex shader."); return -1; }
	err = OpenGL_shader(barstat.prog, GL_FRAGMENT_SHADER, FragShader_barstat);	if(err) { yrLog(0, "Compilation error(s) were in barstat fragment shader."); return -1; }
	glLinkProgram(barstat.prog);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Barstat shaderprogram failed to link: %x", glerr); return -1; }

	barstat.uni_cam_mat	= glGetUniformLocation(barstat.prog, "cam_mat");
	barstat.uni_value	= glGetUniformLocation(barstat.prog, "value");
	barstat.uni_color	= glGetUniformLocation(barstat.prog, "color");
	barstat.uni_row		= glGetUniformLocation(barstat.prog, "row");
	return 0;
}

static void render_barstat_cleanup(void)
{
	glUseProgram(0);
	glDeleteProgram(barstat.prog);
	barstat.prog = 0;
}

static void render_barstat_frame(int lr)
{
	if(yrSystem_debugmode())
	{
		if(lr == 0) {
			//prep display value
			barstat.display_value[bsTimeQuads] = barstat.value[bsTimeQuads] * 90;
			barstat.display_value[bsTimeRender] = barstat.value[bsTimeRender] * 90;
			barstat.display_value[bsTimeRender] += barstat.display_value[bsTimeQuads];
			barstat.display_value[bsTimeInteraction] = barstat.value[bsTimeInteraction] * 90;
			barstat.display_value[bsTimeInteraction] += barstat.display_value[bsTimeRender];
			barstat.display_value[bsTimeOther] = barstat.value[bsTimeOther] * 90;
			barstat.display_value[bsTimeOther] += barstat.display_value[bsTimeInteraction];
			barstat.display_value[bsBlankReserve] = barstat.value[bsBlankReserve];
			barstat.display_value[bsImporantQueue] = barstat.value[bsImporantQueue];
			barstat.display_value[bsFullAvailable] = 1.0f;
			barstat.display_value[bsMipsAvailable] = 0.666f;
			barstat.display_value[bsIdxAvailable] = 0.333f;
			//prep display colors
			barstat.color[bsFullAvailable] = barstat.value[bsFullAvailable] ? 0xFF8080FFul : 0xFF000000ul;
			barstat.color[bsMipsAvailable] = barstat.value[bsMipsAvailable] ? 0xFF80FF80ul : 0xFF000000ul;
			barstat.color[bsIdxAvailable] = barstat.value[bsIdxAvailable] ? 0xFFFF8080ul : 0x0FF00000ul;
		}
		//render bars
		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
		glUseProgram(barstat.prog);
		glBindVertexArray(unitquad.vao);
		glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);

		//backgrounds
		glUniform1f(barstat.uni_value, 1.0f);
		glUniform4f(barstat.uni_color, 0.0f, 0.0f, 0.0f, 0.5f);
		mat4f* cam = lr ? &camera.eye_from_hmd.left : &camera.eye_from_hmd.right;
		glUniformMatrix4fv(barstat.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
		for(int row = 0; row < 4; ++row)
		{
			glUniform1i(barstat.uni_row, row);
			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
		}

		//bars
		for(size_t i = 0; i < bs_end; i += 1)
		{
			yrColor clr = barstat.color[i];
			float clr_r = yrColor_red(clr);
			float clr_g = yrColor_green(clr);
			float clr_b = yrColor_blue(clr);
			float clr_a = yrColor_alpha(clr);
			glUniform4f(barstat.uni_color, clr_r, clr_g, clr_b, clr_a);
			glUniform1f(barstat.uni_value, barstat.display_value[i]);
			glUniform1i(barstat.uni_row, barstat.row[i]);

			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
		}

		GLERRCHECK;
	}
}

void yrRender_barstat(enum yrBarStat id, float value)
{
	switch(id) {
	case bsTimeInteraction:
	case bsTimeOther:
	case bsTimeRender:
	case bsTimeQuads:
		barstat.value[id] = (value > barstat.value[id]) ?  value : (barstat.value[id]*0.8f + value*0.2f);
		break;
	case bsBlankReserve:
	case bsImporantQueue:
	case bsFullAvailable:
	case bsMipsAvailable:
	case bsIdxAvailable:
		barstat.value[id] = value;
		break;;
	}
}

/*************
* Color picker
**************/
static struct {
	mat4f m;
	float rg;
	float yb;
	float lum;
	int do_render;

	GLuint prog_rgb;
	GLuint prog_lum;
	GLint uni_cam_mat;
	GLint uni_pick_mat;
	GLint uni_color_gbl;
}
colorpick = {0};

static int render_colorpick_setup(void);
static void render_colorpick_frame(int lr);
static void render_colorpick_cleanup(void);

static int render_colorpick_setup(void)
{
	memset(&colorpick, 0, sizeof(colorpick));
	
	//create colorpick programs
	colorpick.prog_rgb = glCreateProgram();
	int err = 0;
	GLuint glerr = 0;
	err = OpenGL_shader(colorpick.prog_rgb, GL_VERTEX_SHADER, VtxShader_colorpick_rgb);		if(err) { yrLog(0, "Compilation error(s) were in colorpick vertex shader."); return -1; }
	err = OpenGL_shader(colorpick.prog_rgb, GL_FRAGMENT_SHADER, FragShader_colorpick_rgb);	if(err) { yrLog(0, "Compilation error(s) were in colorpick fragment shader."); return -1; }
	glLinkProgram(colorpick.prog_rgb);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Colorpick shaderprogram failed to link: %x", glerr); return -1; }

	colorpick.prog_lum = glCreateProgram();
	err = OpenGL_shader(colorpick.prog_lum, GL_VERTEX_SHADER, VtxShader_colorpick_lum);		if(err) { yrLog(0, "Compilation error(s) were in colorpick vertex shader."); return -1; }
	err = OpenGL_shader(colorpick.prog_lum, GL_FRAGMENT_SHADER, FragShader_colorpick_lum);	if(err) { yrLog(0, "Compilation error(s) were in colorpick fragment shader."); return -1; }
	glLinkProgram(colorpick.prog_lum);
	glerr = glGetError();														if(glerr != GL_NO_ERROR) { yrLog(0, "Colorpick shaderprogram failed to link: %x", glerr); return -1; }

	colorpick.uni_cam_mat	= glGetUniformLocation(colorpick.prog_rgb, "cam_mat");
	colorpick.uni_pick_mat	= glGetUniformLocation(colorpick.prog_rgb, "pick_mat");
	colorpick.uni_color_gbl	= glGetUniformLocation(colorpick.prog_rgb, "color_gbl");
	return 0;
}

static void render_colorpick_cleanup(void)
{
	glUseProgram(0);
	glDeleteProgram(colorpick.prog_rgb);
	glDeleteProgram(colorpick.prog_lum);
	colorpick.prog_rgb = 0;
	colorpick.prog_lum = 0;
}

static void render_colorpick_frame(int lr)
{
	if(colorpick.do_render) {
		colorpick.do_render = 1 - lr;
		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
		mat4f* cam = lr ? &camera.eye_from_world.left : &camera.eye_from_world.right;

		//hue picker
		glUseProgram(colorpick.prog_rgb);
		glBindVertexArray(unitquad.vao);
		glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
		glUniform4f(colorpick.uni_color_gbl, colorpick.rg, colorpick.yb, colorpick.lum, 1.0f);
		glUniformMatrix4fv(colorpick.uni_pick_mat, 1, GL_FALSE, (const GLfloat*) &colorpick.m);

		glUniformMatrix4fv(colorpick.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
		glDrawArrays(GL_TRIANGLES, 1, 3);

		//luminance picker
		glUseProgram(colorpick.prog_lum);
		glBindVertexArray(unitquad.vao);
		glBindBuffer(GL_ARRAY_BUFFER, unitquad.vbo);
		glUniform4f(colorpick.uni_color_gbl, colorpick.rg, colorpick.yb, colorpick.lum, 1.0f);
		glUniformMatrix4fv(colorpick.uni_pick_mat, 1, GL_FALSE, (const GLfloat*) &colorpick.m);

		glUniformMatrix4fv(colorpick.uni_cam_mat, 1, GL_FALSE, (const GLfloat*) cam);
		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
	}
}

void yrRender_colorpick(mat4f m, float rg, float yb, float lum)
{
	colorpick.do_render = 1;
	colorpick.m = m;
	colorpick.rg = rg;
	colorpick.yb = yb;
	colorpick.lum = lum;
}

/****************************************
* System functions (init, tick, shutdown)
*****************************************/
static size_t get_vram(void)
{
	GLint info[4] = {0};
	glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, info);
	if(!info[0]) {
		glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, info);
	}
	if(!info[0]) {
		info[0] = 2048 * 1024;
	}
	glGetError(); //clear opengl error
	return 1024ull * (size_t) info[0];
}

static int yrRender_init(void* loadtoken)
{
	size_t vram = get_vram();
	yrSystem_set_tilestore_bytes(vram/2);
	int err = 0;
	err = render_setup();					if(err) goto onerror;
	err = render_quad_setup();				if(err) goto onerror;
	err = render_overlay_setup();			if(err) goto onerror;
	err = render_modeltex_setup();			if(err) goto onerror;
	err = render_trackedoverlay_setup();	if(err) goto onerror;
	err = render_beam_setup();				if(err) goto onerror;
	err = render_teleport_setup();			if(err) goto onerror;
	err = render_barstat_setup();			if(err) goto onerror;
	err = render_colorpick_setup();			if(err) goto onerror;
	err = render_load(NULL);				if(err) goto onerror;
	return 0;
onerror:
	yrRender_shutdown();
	return err;
}

static int yrRender_shutdown(void)
{
	render_colorpick_cleanup();
	render_barstat_cleanup();
	render_teleport_cleanup();
	render_beam_cleanup();
	render_modeltex_cleanup();
	render_trackedoverlay_cleanup();
	render_overlay_cleanup();
	render_quad_cleanup();
	render_cleanup();
	return 0;
}

static int yrRender_tick(void)
{
	uint64_t t_render_a = yr_get_microtime();
	for(int lr = 0; lr < 2; ++lr)
	{
		render_frame_pre(lr);
		render_quad_frame(lr);
		render_modeltex_frame(lr);
		render_trackedoverlay_frame(lr); /*overlay on tracked model*/
		render_overlay_frame(lr); /*overlay on quad*/
		render_beam_frame(lr);
		render_teleport_frame(lr);
		render_barstat_frame(lr); /*debug cpu stats osd*/
		render_colorpick_frame(lr);
	}
	render_frame_post();
	uint64_t t_render_b = yr_get_microtime();
	{GLuint glerr = glGetError(); if(glerr) yrLog(0, "OpenGL error detected in frame: %x", glerr);}
	//report timings
	float t_render = (t_render_b - t_render_a)/1000000.0f;
	yrRender_barstat(bsTimeRender, t_render - quads.t_quadprep);
	yrRender_barstat(bsTimeQuads, quads.t_quadprep);
	return 0;
}