/*
 * This file is part of the D3wasm project (http://www.continuation-labs.com/projects/d3wasm)
 * Copyright (c) 2019 Gabriel Cuvillier.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <GLES3/gl3.h>
#include "sys/platform.h"
#include "renderer/VertexCache.h"

#include "tr_local.h"

#include "glsl/glsl_shaders.h"

shaderProgram_t interactionShader;
shaderProgram_t interactionPhongShader;
shaderProgram_t fogShader;
shaderProgram_t blendLightShader;
shaderProgram_t zfillShader;
shaderProgram_t zfillClipShader;
shaderProgram_t diffuseMapShader;
shaderProgram_t diffuseCubeShader;
shaderProgram_t skyboxCubeShader;
shaderProgram_t reflectionCubeShader;
shaderProgram_t stencilShadowShader;

#define ORTHO_PROJECTION			0
#define NORMAL_PROJECTION			1
#define WEAPON_PROJECTION			2
#define DEPTH_HACK_PROJECTION		3
#define NUM_DEPTH_HACK_PROJECTIONS  50
GLuint		viewMatricesBuffer;

bool        projectionMatricesSet = false;
GLuint		projectionMatricesBuffer[DEPTH_HACK_PROJECTION + NUM_DEPTH_HACK_PROJECTIONS + 1];

#define ATTR_VERTEX     0   // Don't change this, as WebGL require the vertex attrib 0 to be always bound
#define ATTR_COLOR      1
#define ATTR_TEXCOORD   2
#define ATTR_NORMAL     3
#define ATTR_TANGENT    4
#define ATTR_BITANGENT  5

/*
====================
GL_UseProgram
====================
*/
void GL_UseProgram(shaderProgram_t* program) {
	if ( backEnd.glState.currentProgram == program ) {
		return;
	}

	qglUseProgram(program ? program->program : 0);
	backEnd.glState.currentProgram = program;
}

/*
====================
GL_Uniform1fv
====================
*/
static void GL_Uniform1fv(GLint location, const GLfloat* value) {
	qglUniform1fv(*( GLint * )((char*) backEnd.glState.currentProgram + location), 1, value);
}

/*
====================
GL_Uniform1iv
====================
*/
static void GL_Uniform1iv(GLint location, const GLint* value) {
	qglUniform1iv(*( GLint * )((char*) backEnd.glState.currentProgram + location), 1, value);
}

/*
====================
GL_Uniform4fv
====================
*/
static void GL_Uniform4fv(GLint location, const GLfloat* value) {
	qglUniform4fv(*( GLint * )((char*) backEnd.glState.currentProgram + location), 1, value);
}

/*
====================
GL_UniformMatrix4fv
====================
*/
static void GL_UniformMatrix4fv(GLint location, const GLfloat* value) {
	qglUniformMatrix4fv(*( GLint * )((char*) backEnd.glState.currentProgram + location), 1, GL_FALSE, value);
}

/*
====================
GL_ViewMatricesUniformBuffer
====================
*/
static void GL_ViewMatricesUniformBuffer(const float value[32]) {

	// Update the scene matrices.
	glBindBuffer(GL_UNIFORM_BUFFER, viewMatricesBuffer);
	float* viewMatrices = (float*)glMapBufferRange(
			GL_UNIFORM_BUFFER,
			0,
			2 * 16 * sizeof(float),
			GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

	if (viewMatrices == NULL)
	{
		common->Error("View Matrices Uniform Buffer is NULL");
		return;
	}

	memcpy((char*)viewMatrices, value, 32 * sizeof(float));

	glUnmapBuffer(GL_UNIFORM_BUFFER);
	qglBindBuffer(GL_UNIFORM_BUFFER, 0);
}

/*
====================
GL_ProjectionMatricesUniformBuffer
====================
*/
static void GL_ProjectionMatricesUniformBuffer(GLint projectionMatricesBuffer, const float value[16]) {

	// Update the scene matrices.
	glBindBuffer(GL_UNIFORM_BUFFER, projectionMatricesBuffer);
	float* projectionMatrix = (float*)glMapBufferRange(
			GL_UNIFORM_BUFFER,
			0,
			16 * sizeof(float),
			GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

	if (projectionMatrix == NULL)
	{
		common->Error("Projection Matrices Uniform Buffer is NULL");
		return;
	}

	memcpy((char*)projectionMatrix, value, 16 * sizeof(float));

	glUnmapBuffer(GL_UNIFORM_BUFFER);
	qglBindBuffer(GL_UNIFORM_BUFFER, 0);
}

/*
====================
GL_EnableVertexAttribArray
====================
*/
void GL_EnableVertexAttribArray(GLuint index) {
	qglEnableVertexAttribArray(index);
}

/*
====================
GL_DisableVertexAttribArray
====================
*/
void GL_DisableVertexAttribArray(GLuint index) {
	qglDisableVertexAttribArray(index);
}

/*
====================
GL_VertexAttribPointer
====================
*/
static void GL_VertexAttribPointer(GLuint index, GLint size, GLenum type,
                                   GLboolean normalized, GLsizei stride,
                                   const GLvoid* pointer) {
	qglVertexAttribPointer(*( GLint * )((char*) backEnd.glState.currentProgram + index),
	                       size, type, normalized, stride, pointer);
}

/*
=================
R_LoadGLSLShader

loads GLSL vertex or fragment shaders
=================
*/
static void R_LoadGLSLShader(const char* buffer, shaderProgram_t* shaderProgram, GLenum type) {
	if ( !glConfig.isInitialized ) {
		return;
	}

	switch ( type ) {
	case GL_VERTEX_SHADER:
		// create vertex shader
		shaderProgram->vertexShader = qglCreateShader(GL_VERTEX_SHADER);
		qglShaderSource(shaderProgram->vertexShader, 1, (const GLchar**) &buffer, 0);
		qglCompileShader(shaderProgram->vertexShader);
		break;
	case GL_FRAGMENT_SHADER:
		// create fragment shader
		shaderProgram->fragmentShader = qglCreateShader(GL_FRAGMENT_SHADER);
		qglShaderSource(shaderProgram->fragmentShader, 1, (const GLchar**) &buffer, 0);
		qglCompileShader(shaderProgram->fragmentShader);
		break;
	default:
		common->Printf("R_LoadGLSLShader: no type\n");
		return;
	}
}

/*
=================
R_LinkGLSLShader

links the GLSL vertex and fragment shaders together to form a GLSL program
=================
*/
static bool R_LinkGLSLShader(shaderProgram_t* shaderProgram, const char* name) {
	char buf[BUFSIZ];
	int len;
	GLint linked;

	shaderProgram->program = qglCreateProgram();

	qglAttachShader(shaderProgram->program, shaderProgram->vertexShader);
	qglAttachShader(shaderProgram->program, shaderProgram->fragmentShader);

	// Bind attributes locations
	qglBindAttribLocation(shaderProgram->program, ATTR_VERTEX, "attr_Vertex");
	qglBindAttribLocation(shaderProgram->program, ATTR_COLOR, "attr_Color");
	qglBindAttribLocation(shaderProgram->program, ATTR_TEXCOORD, "attr_TexCoord");
	qglBindAttribLocation(shaderProgram->program, ATTR_NORMAL, "attr_Normal");
	qglBindAttribLocation(shaderProgram->program, ATTR_TANGENT, "attr_Tangent");
	qglBindAttribLocation(shaderProgram->program, ATTR_BITANGENT, "attr_Bitangent");

	qglLinkProgram(shaderProgram->program);

	qglGetProgramiv(shaderProgram->program, GL_LINK_STATUS, &linked);

	if ( com_developer.GetBool()) {
		qglGetShaderInfoLog(shaderProgram->vertexShader, sizeof(buf), &len, buf);
		common->Printf("VS:\n%.*s\n", len, buf);
		qglGetShaderInfoLog(shaderProgram->fragmentShader, sizeof(buf), &len, buf);
		common->Printf("FS:\n%.*s\n", len, buf);
	}

	if ( !linked ) {
		common->Error("R_LinkGLSLShader: program failed to link: %s\n", name);
		return false;
	}

	return true;
}

/*
=================
R_ValidateGLSLProgram

makes sure GLSL program is valid
=================
*/
static bool R_ValidateGLSLProgram(shaderProgram_t* shaderProgram) {
	GLint validProgram;

	qglValidateProgram(shaderProgram->program);

	qglGetProgramiv(shaderProgram->program, GL_VALIDATE_STATUS, &validProgram);

	if ( !validProgram ) {
		common->Printf("R_ValidateGLSLProgram: program invalid\n");
		return false;
	}

	return true;
}

/*
=================
RB_GLSL_GetUniformLocations

=================
*/
static void RB_GLSL_GetUniformLocations(shaderProgram_t* shader) {
	int i;
	char buffer[32];

	GL_UseProgram(shader);

	shader->localLightOrigin = qglGetUniformLocation(shader->program, "u_lightOrigin");

	//May need to move this to the shader matrices uniform block?
	shader->localViewOrigin = qglGetUniformLocation(shader->program, "u_viewOrigin");
	shader->lightProjection = qglGetUniformLocation(shader->program, "u_lightProjection");
	shader->bumpMatrixS = qglGetUniformLocation(shader->program, "u_bumpMatrixS");
	shader->bumpMatrixT = qglGetUniformLocation(shader->program, "u_bumpMatrixT");
	shader->diffuseMatrixS = qglGetUniformLocation(shader->program, "u_diffuseMatrixS");
	shader->diffuseMatrixT = qglGetUniformLocation(shader->program, "u_diffuseMatrixT");
	shader->specularMatrixS = qglGetUniformLocation(shader->program, "u_specularMatrixS");
	shader->specularMatrixT = qglGetUniformLocation(shader->program, "u_specularMatrixT");
	shader->colorModulate = qglGetUniformLocation(shader->program, "u_colorModulate");
	shader->colorAdd = qglGetUniformLocation(shader->program, "u_colorAdd");
	shader->fogColor = qglGetUniformLocation(shader->program, "u_fogColor");
	shader->diffuseColor = qglGetUniformLocation(shader->program, "u_diffuseColor");
	shader->specularColor = qglGetUniformLocation(shader->program, "u_specularColor");
	shader->glColor = qglGetUniformLocation(shader->program, "u_glColor");
	shader->alphaTest = qglGetUniformLocation(shader->program, "u_alphaTest");
	shader->specularExponent = qglGetUniformLocation(shader->program, "u_specularExponent");

	shader->modelMatrix = qglGetUniformLocation(shader->program, "u_modelMatrix");

	//Shader Matrices for the View Matrices
	GLuint viewMatricesUniformLocation = glGetUniformBlockIndex(shader->program, "ViewMatrices");
	int numBufferBindings = 0;
	shader->viewMatricesBinding = numBufferBindings++;
	glUniformBlockBinding(
			shader->program,
			viewMatricesUniformLocation,
			shader->viewMatricesBinding);

	//Shader Matrices for the Projection Matrix
	GLuint projectionMatrixUniformLocation = glGetUniformBlockIndex(shader->program, "ProjectionMatrix");
	shader->projectionMatrixBinding = numBufferBindings++;
	glUniformBlockBinding(
			shader->program,
			projectionMatrixUniformLocation,
			shader->projectionMatrixBinding);

	shader->modelViewMatrix = qglGetUniformLocation(shader->program, "u_modelViewMatrix");
	shader->textureMatrix = qglGetUniformLocation(shader->program, "u_textureMatrix");
	shader->clipPlane = qglGetUniformLocation(shader->program, "u_clipPlane");
	shader->fogMatrix = qglGetUniformLocation(shader->program, "u_fogMatrix");

	shader->attr_TexCoord = qglGetAttribLocation(shader->program, "attr_TexCoord");
	shader->attr_Tangent = qglGetAttribLocation(shader->program, "attr_Tangent");
	shader->attr_Bitangent = qglGetAttribLocation(shader->program, "attr_Bitangent");
	shader->attr_Normal = qglGetAttribLocation(shader->program, "attr_Normal");
	shader->attr_Vertex = qglGetAttribLocation(shader->program, "attr_Vertex");
	shader->attr_Color = qglGetAttribLocation(shader->program, "attr_Color");

	// Init default values
	for ( i = 0; i < MAX_FRAGMENT_IMAGES; i++ ) {
		idStr::snPrintf(buffer, sizeof(buffer), "u_fragmentMap%d", i);
		shader->u_fragmentMap[i] = qglGetUniformLocation(shader->program, buffer);
		qglUniform1i(shader->u_fragmentMap[i], i);
	}

	for ( i = 0; i < MAX_FRAGMENT_IMAGES; i++ ) {
		idStr::snPrintf(buffer, sizeof(buffer), "u_fragmentCubeMap%d", i);
		shader->u_fragmentCubeMap[i] = qglGetUniformLocation(shader->program, buffer);
		qglUniform1i(shader->u_fragmentCubeMap[i], i);
	}

	if (shader->textureMatrix >= 0) {
		// Load identity matrix for Texture marix
		GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
	}

	static const GLfloat one[1] = { 1.0f };
	if (shader->alphaTest >= 0) {
		// Alpha test always pass by default
		GL_Uniform1fv(offsetof(shaderProgram_t, alphaTest), one);
	}

	if (shader->colorModulate >= 0) {
		static const GLfloat zero[1] = { 0.0f };
		GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), zero);
	}

	if (shader->colorAdd >= 0) {
		GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), one);
	}

	GL_CheckErrors();

	GL_UseProgram(NULL);
}

/*
=================
RB_GLSL_InitShaders

=================
*/
static bool RB_GLSL_InitShaders(void) {

	//Generate buffer for 2 * view matrices
	qglGenBuffers(1, &viewMatricesBuffer);
    glBindBuffer(GL_UNIFORM_BUFFER, viewMatricesBuffer);
    glBufferData(
            GL_UNIFORM_BUFFER,
            2 * 16 * sizeof(float),
            NULL,
            GL_STATIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    for (int i = 0; i <= (NUM_DEPTH_HACK_PROJECTIONS+DEPTH_HACK_PROJECTION); ++i)
	{
		qglGenBuffers(1, &projectionMatricesBuffer[i]);
		glBindBuffer(GL_UNIFORM_BUFFER, projectionMatricesBuffer[i]);
		glBufferData(
				GL_UNIFORM_BUFFER,
				16 * sizeof(float),
				NULL,
				GL_STATIC_DRAW);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
	}

    // main Interaction shader
	common->Printf("Loading main interaction shader\n");
	memset(&interactionShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(interactionShaderVP, &interactionShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(interactionShaderFP, &interactionShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&interactionShader, "interaction") && !R_ValidateGLSLProgram(&interactionShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&interactionShader);
	}

	// main Interaction shader, Phong version
	common->Printf("Loading main interaction shader (Phong) \n");
	memset(&interactionPhongShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(interactionPhongShaderVP, &interactionPhongShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(interactionPhongShaderFP, &interactionPhongShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&interactionPhongShader, "interactionPhong") &&
	        !R_ValidateGLSLProgram(&interactionPhongShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&interactionPhongShader);
	}

	// default diffuse shader
	common->Printf("Loading default diffuse shader\n");
	memset(&diffuseMapShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(diffuseMapShaderVP, &diffuseMapShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(diffuseMapShaderFP, &diffuseMapShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&diffuseMapShader, "diffuseMap") && !R_ValidateGLSLProgram(&diffuseMapShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&diffuseMapShader);
	}

	// Skybox cubemap shader
	common->Printf("Loading skybox cubemap shader\n");
	memset(&skyboxCubeShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(skyboxCubeShaderVP, &skyboxCubeShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(cubeMapShaderFP, &skyboxCubeShader, GL_FRAGMENT_SHADER);   // Use the common "cubeMapShaderFP"

	if ( !R_LinkGLSLShader(&skyboxCubeShader, "skyboxCube") && !R_ValidateGLSLProgram(&skyboxCubeShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&skyboxCubeShader);
	}

	// Reflection cubemap shader
	common->Printf("Loading reflection cubemap shader\n");
	memset(&reflectionCubeShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(reflectionCubeShaderVP, &reflectionCubeShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(cubeMapShaderFP, &reflectionCubeShader, GL_FRAGMENT_SHADER); // Use the common "cubeMapShaderFP"

	if ( !R_LinkGLSLShader(&reflectionCubeShader, "reflectionCube") &&
	        !R_ValidateGLSLProgram(&reflectionCubeShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&reflectionCubeShader);
	}

	// Diffuse cubemap shader
	common->Printf("Loading diffuse cubemap shader\n");
	memset(&diffuseCubeShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(diffuseCubeShaderVP, &diffuseCubeShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(cubeMapShaderFP, &diffuseCubeShader, GL_FRAGMENT_SHADER); // Use the common "cubeMapShaderFP"

	if ( !R_LinkGLSLShader(&diffuseCubeShader, "diffuseCube") && !R_ValidateGLSLProgram(&diffuseCubeShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&diffuseCubeShader);
	}

	// Z Fill shader
	common->Printf("Loading Zfill shader\n");
	memset(&zfillShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(zfillShaderVP, &zfillShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(zfillShaderFP, &zfillShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&zfillShader, "zfill") && !R_ValidateGLSLProgram(&zfillShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&zfillShader);
	}

	// Z Fill shader, Clip planes version
	common->Printf("Loading Zfill shader (Clip plane version)\n");
	memset(&zfillClipShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(zfillClipShaderVP, &zfillClipShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(zfillClipShaderFP, &zfillClipShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&zfillClipShader, "zfillClip") && !R_ValidateGLSLProgram(&zfillClipShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&zfillClipShader);
	}

	// Fog shader
	common->Printf("Loading Fog shader\n");
	memset(&fogShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(fogShaderVP, &fogShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(fogShaderFP, &fogShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&fogShader, "fog") && !R_ValidateGLSLProgram(&fogShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&fogShader);
	}

	// BlendLight shader
	common->Printf("Loading BlendLight shader\n");
	memset(&blendLightShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(blendLightShaderVP, &blendLightShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(fogShaderFP, &blendLightShader, GL_FRAGMENT_SHADER);       // Reuse the common "FogShaderFP"

	if ( !R_LinkGLSLShader(&blendLightShader, "blendLight") && !R_ValidateGLSLProgram(&blendLightShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&blendLightShader);
	}

	// Stencil shadow shader
	common->Printf("Loading Stencil shadow shader\n");
	memset(&stencilShadowShader, 0, sizeof(shaderProgram_t));

	R_LoadGLSLShader(stencilShadowShaderVP, &stencilShadowShader, GL_VERTEX_SHADER);
	R_LoadGLSLShader(stencilShadowShaderFP, &stencilShadowShader, GL_FRAGMENT_SHADER);

	if ( !R_LinkGLSLShader(&stencilShadowShader, "stencilShadow") && !R_ValidateGLSLProgram(&stencilShadowShader)) {
		return false;
	} else {
		RB_GLSL_GetUniformLocations(&stencilShadowShader);
	}

	return true;
}

/*
==================
R_ReloadGLSLPrograms_f
==================
*/
void R_ReloadGLSLPrograms_f(const idCmdArgs& args) {
	common->Printf("----- R_ReloadGLSLPrograms -----\n");

	if ( !RB_GLSL_InitShaders()) {
		common->Printf("GLSL shaders failed to init.\n");
	}

	common->Printf("-------------------------------\n");
}

/*
=================
RB_ComputeProjection

Compute the required projection matrix depth hacks
=================
*/
void RB_ComputeProjection(bool weaponDepthHack, float modelDepthHack, float *projection) {
	// Get the projection matrix
	float localProjectionMatrix[16];
	memcpy(localProjectionMatrix, backEnd.viewDef->projectionMatrix, sizeof(localProjectionMatrix));

	// Quick and dirty hacks on the projection matrix
	if ( weaponDepthHack ) {
		localProjectionMatrix[14] = backEnd.viewDef->projectionMatrix[14] * 0.25;
	} else if ( modelDepthHack != 0.0 ) {
		localProjectionMatrix[14] = backEnd.viewDef->projectionMatrix[14] - modelDepthHack;
	}

	memcpy(projection, localProjectionMatrix, sizeof(localProjectionMatrix));
}

/*
=================
RB_CalculateProjection

Compute which projection matrix buffer should be used
=================
*/

GLuint RB_CalculateProjection(const drawSurf_t * const surf) {
	GLuint result = NORMAL_PROJECTION;
	if ( surf->space->weaponDepthHack ) {
		result = WEAPON_PROJECTION;
	} else if ( surf->space->modelDepthHack != 0.0 ) {
		result = DEPTH_HACK_PROJECTION + (GLuint)(surf->space->modelDepthHack * (float)NUM_DEPTH_HACK_PROJECTIONS);
	} else if (
	        //Is this set up as an orthographic projection?
            backEnd.viewDef->projectionMatrix[0] == 2.0f / 640.0f &&
			backEnd.viewDef->projectionMatrix[5] == -2.0f / 480.0f &&
			backEnd.viewDef->projectionMatrix[10] == -2.0f / 1.0f &&
			backEnd.viewDef->projectionMatrix[12] == -1.0f &&
			backEnd.viewDef->projectionMatrix[13] == 1.0f &&
			backEnd.viewDef->projectionMatrix[14] == -1.0f &&
			backEnd.viewDef->projectionMatrix[15] == 1.0f
			)
	{
		result = ORTHO_PROJECTION;
	}

	return result;
}

/*
==================
RB_GLSL_DrawInteraction
==================
*/
static void RB_GLSL_DrawInteraction(const drawInteraction_t* din) {
	static const GLfloat zero[1] = { 0 };
	static const GLfloat one[1] = { 1 };
	static const GLfloat oneScaled[1] = { 1 / 255.0f };
	static const GLfloat negOneScaled[1] = { -1.0f / 255.0f };

	// load all the vertex program parameters
	GL_Uniform4fv(offsetof(shaderProgram_t, localLightOrigin), din->localLightOrigin.ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, localViewOrigin), din->localViewOrigin.ToFloatPtr());
	GL_UniformMatrix4fv(offsetof(shaderProgram_t, lightProjection), din->lightProjection.ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, bumpMatrixS), din->bumpMatrix[0].ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, bumpMatrixT), din->bumpMatrix[1].ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, diffuseMatrixS), din->diffuseMatrix[0].ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, diffuseMatrixT), din->diffuseMatrix[1].ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, specularMatrixS), din->specularMatrix[0].ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, specularMatrixT), din->specularMatrix[1].ToFloatPtr());

	switch ( din->vertexColor ) {
	case SVC_MODULATE: {
		GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), oneScaled);
		GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), zero);
		break;
	}
	case SVC_INVERSE_MODULATE: {
		GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), negOneScaled);
		GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), one);
		break;
	}
	default:
	case SVC_IGNORE:
		// This is already the default values (zero, one)
		break;
	}

	// set the constant colors
	GL_Uniform4fv(offsetof(shaderProgram_t, diffuseColor), din->diffuseColor.ToFloatPtr());
	GL_Uniform4fv(offsetof(shaderProgram_t, specularColor), din->specularColor.ToFloatPtr());

	// set the textures

	// texture 0 will be the per-surface bump map
	// NB: Texture 0 is expected to be active at this point
	din->bumpImage->Bind();

	// texture 1 will be the light falloff texture
	GL_SelectTexture(1);
	din->lightFalloffImage->Bind();

	// texture 2 will be the light projection texture
	GL_SelectTexture(2);
	din->lightImage->Bind();

	// texture 3 is the per-surface diffuse map
	GL_SelectTexture(3);
	din->diffuseImage->Bind();

	// texture 4 is the per-surface specular map
	GL_SelectTexture(4);
	din->specularImage->Bind();

	// Be sure to activate Texture 0 for next interaction, or next pass
	GL_SelectTexture(0);

	// draw it
	RB_DrawElementsWithCounters(din->surf);

	// Restore color modulation state to default values
	if ( din->vertexColor != SVC_IGNORE ) {
		GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), zero);
		GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), one);
	}
}

/*
=============
RB_CreateSingleDrawInteractions

This can be used by different draw_* backends to decompose a complex light / surface
interaction into primitive interactions
=============
*/
static void
RB_GLSL_CreateSingleDrawInteractions(const drawSurf_t* surf, void (* DrawInteraction)(const drawInteraction_t*), const viewLight_t* vLight) {
	const idMaterial* surfaceShader = surf->material;
	const float* surfaceRegs = surf->shaderRegisters;
	const idMaterial* lightShader = vLight->lightShader;
	const float* lightRegs = vLight->shaderRegisters;
	drawInteraction_t inter;

	if ( r_skipInteractions.GetBool() || !surf->geoFrontEnd || !surf->ambientCache ) {
		return;
	}

	// change the scissor if needed
	if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals(surf->scissorRect)) {
		backEnd.currentScissor = surf->scissorRect;
		if (( backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1 ) < 0.0f ||
		        ( backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ) < 0.0f ) {
			backEnd.currentScissor = backEnd.viewDef->scissor;
		}
		qglScissor(backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
		           backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
		           backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
		           backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1);
	}

	inter.surf = surf;
	inter.lightFalloffImage = vLight->falloffImage;

	R_GlobalPointToLocal(surf->space->modelMatrix, vLight->globalLightOrigin, inter.localLightOrigin.ToVec3());
	R_GlobalPointToLocal(surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, inter.localViewOrigin.ToVec3());
	inter.localLightOrigin[3] = 0;
	inter.localViewOrigin[3] = 1;
	inter.ambientLight = lightShader->IsAmbientLight();

	// the base projections may be modified by texture matrix on light stages
	idPlane lightProject[4];

	for ( int i = 0; i < 4; i++ ) {
		R_GlobalPlaneToLocal(surf->space->modelMatrix, vLight->lightProject[i], lightProject[i]);
	}

	for ( int lightStageNum = 0; lightStageNum < lightShader->GetNumStages(); lightStageNum++ ) {
		const shaderStage_t* lightStage = lightShader->GetStage(lightStageNum);

		// ignore stages that fail the condition
		if ( !lightRegs[lightStage->conditionRegister] ) {
			continue;
		}

		inter.lightImage = lightStage->texture.image;

		inter.lightProjection[0] = lightProject[0].ToVec4(); // S
		inter.lightProjection[1] = lightProject[1].ToVec4(); // T
		inter.lightProjection[2] = lightProject[3].ToVec4(); // CAUTION! this is the 4th vector. R = Falloff
		inter.lightProjection[3] = lightProject[2].ToVec4(); // CAUTION! this is the 3rd vector. Q

		// now multiply the texgen by the light texture matrix
		if ( lightStage->texture.hasMatrix ) {
			float lightTextureMatrix[16];
			RB_GetShaderTextureMatrix(lightRegs, &lightStage->texture, lightTextureMatrix);
			RB_BakeTextureMatrixIntoTexgen(inter.lightProjection, lightTextureMatrix);
		}

		inter.bumpImage = NULL;
		inter.specularImage = NULL;
		inter.diffuseImage = NULL;
		inter.diffuseColor[0] = inter.diffuseColor[1] = inter.diffuseColor[2] = inter.diffuseColor[3] = 0;
		inter.specularColor[0] = inter.specularColor[1] = inter.specularColor[2] = inter.specularColor[3] = 0;

		float lightColor[4];

		const float lightscale = r_lightScale.GetFloat();
		lightColor[0] = lightscale * lightRegs[lightStage->color.registers[0]];
		lightColor[1] = lightscale * lightRegs[lightStage->color.registers[1]];
		lightColor[2] = lightscale * lightRegs[lightStage->color.registers[2]];
		lightColor[3] = lightRegs[lightStage->color.registers[3]];

		// go through the individual stages
		for ( int surfaceStageNum = 0; surfaceStageNum < surfaceShader->GetNumStages(); surfaceStageNum++ ) {
			const shaderStage_t* surfaceStage = surfaceShader->GetStage(surfaceStageNum);

			switch ( surfaceStage->lighting ) {
			case SL_AMBIENT: {
				// ignore ambient stages while drawing interactions
				break;
			}
			case SL_BUMP: {
				// ignore stage that fails the condition
				if ( !surfaceRegs[surfaceStage->conditionRegister] ) {
					break;
				}

				// draw any previous interaction
				RB_SubmittInteraction(&inter, DrawInteraction);
				inter.diffuseImage = NULL;
				inter.specularImage = NULL;
				RB_SetDrawInteraction(surfaceStage, surfaceRegs, &inter.bumpImage, inter.bumpMatrix, NULL);
				break;
			}
			case SL_DIFFUSE: {
				// ignore stage that fails the condition
				if ( !surfaceRegs[surfaceStage->conditionRegister] ) {
					break;
				}

				if ( inter.diffuseImage ) {
					RB_SubmittInteraction(&inter, DrawInteraction);
				}

				RB_SetDrawInteraction(surfaceStage, surfaceRegs, &inter.diffuseImage,
				                      inter.diffuseMatrix, inter.diffuseColor.ToFloatPtr());
				inter.diffuseColor[0] *= lightColor[0];
				inter.diffuseColor[1] *= lightColor[1];
				inter.diffuseColor[2] *= lightColor[2];
				inter.diffuseColor[3] *= lightColor[3];
				inter.vertexColor = surfaceStage->vertexColor;
				break;
			}
			case SL_SPECULAR: {
				// ignore stage that fails the condition
				if ( !surfaceRegs[surfaceStage->conditionRegister] ) {
					break;
				}

				if ( inter.specularImage ) {
					RB_SubmittInteraction(&inter, DrawInteraction);
				}

				RB_SetDrawInteraction(surfaceStage, surfaceRegs, &inter.specularImage,
				                      inter.specularMatrix, inter.specularColor.ToFloatPtr());
				inter.specularColor[0] *= lightColor[0];
				inter.specularColor[1] *= lightColor[1];
				inter.specularColor[2] *= lightColor[2];
				inter.specularColor[3] *= lightColor[3];
				inter.vertexColor = surfaceStage->vertexColor;
				break;
			}
			}
		}

		// draw the final interaction
		RB_SubmittInteraction(&inter, DrawInteraction);
	}
}

/*
=============
RB_GLSL_CreateDrawInteractions

=============
*/
static void RB_GLSL_CreateDrawInteractions(const drawSurf_t* surf, const viewLight_t* vLight, const int depthFunc = GLS_DEPTHFUNC_EQUAL) {
	if ( !surf ) {
		return;
	}

	// perform setup here that will be constant for all interactions
	GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | depthFunc);

	// bind the vertex and fragment shader
	if ( r_usePhong.GetBool()) {
		GL_UseProgram(&interactionPhongShader);

		// Set the specular exponent now (NB: it could be cached instead)
		const float f = r_specularExponent.GetFloat();
		GL_Uniform1fv(offsetof(shaderProgram_t, specularExponent), &f);
	} else {
		GL_UseProgram(&interactionShader);
	}

	glBindBufferBase(
		GL_UNIFORM_BUFFER,
		backEnd.glState.currentProgram->viewMatricesBinding,
		viewMatricesBuffer);


	// Setup attributes arrays
	// Vertex attribute is always enabled
	// Color attribute is always enabled
	// TexCoord attribute is always enabled
	// Enable the rest
	GL_EnableVertexAttribArray(ATTR_TANGENT);
	GL_EnableVertexAttribArray(ATTR_BITANGENT);
	GL_EnableVertexAttribArray(ATTR_NORMAL);

	backEnd.currentSpace = NULL;

	for ( ; surf; surf = surf->nextOnLight ) {
		// perform setup here that will not change over multiple interaction passes

		if ( surf->space != backEnd.currentSpace ) {
			GL_UniformMatrix4fv(offsetof(shaderProgram_t, modelMatrix), surf->space->modelMatrix);

			glBindBufferBase(
					GL_UNIFORM_BUFFER,
					backEnd.glState.currentProgram->projectionMatrixBinding,
					projectionMatricesBuffer[RB_CalculateProjection(surf)]);
		}

		// Hack Depth Range if necessary
		bool bNeedRestoreDepthRange = false;
		if (surf->space->weaponDepthHack && surf->space->modelDepthHack == 0.0f) {
			qglDepthRangef(0.0f, 0.5f);
			bNeedRestoreDepthRange = true;
		}

		// set the vertex pointers
		idDrawVert* ac = (idDrawVert*) vertexCache.Position(surf->ambientCache);

		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Normal), 3, GL_FLOAT, false, sizeof(idDrawVert),
		                       ac->normal.ToFloatPtr());
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Bitangent), 3, GL_FLOAT, false, sizeof(idDrawVert),
		                       ac->tangents[1].ToFloatPtr());
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Tangent), 3, GL_FLOAT, false, sizeof(idDrawVert),
		                       ac->tangents[0].ToFloatPtr());
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_TexCoord), 2, GL_FLOAT, false, sizeof(idDrawVert),
		                       ac->st.ToFloatPtr());
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert),
		                       ac->xyz.ToFloatPtr());
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Color), 4, GL_UNSIGNED_BYTE, false, sizeof(idDrawVert),
		                       (void*) &ac->color);

		// this may cause RB_GLSL_DrawInteraction to be exacuted multiple
		// times with different colors and images if the surface or light have multiple layers
		RB_GLSL_CreateSingleDrawInteractions(surf, RB_GLSL_DrawInteraction, vLight);

		// Restore the Depth Range in case it have been hacked
		if ( bNeedRestoreDepthRange ) {
			qglDepthRangef( 0.0f, 1.0f );
		}

		backEnd.currentSpace = surf->space;
	}

	backEnd.currentSpace = NULL;

	// Restore attributes arrays
	// Vertex attribute is always enabled
	// Color attribute is always enabled
	// TexCoord attribute is always enabled
	GL_DisableVertexAttribArray(ATTR_TANGENT);
	GL_DisableVertexAttribArray(ATTR_BITANGENT);
	GL_DisableVertexAttribArray(ATTR_NORMAL);
}


/*
=====================
RB_T_GLSL_Shadow

the shadow volumes face INSIDE
=====================
*/
static void RB_T_GLSL_Shadow(const drawSurf_t* surf, const viewLight_t* vLight) {

	//const srfTriangles_t* tri = surf->geo;

	if ( !surf->shadowCache ) {
		return;
	}

	// set the light position for the vertex program to project the rear surfaces
	if ( surf->space != backEnd.currentSpace ) {
		idVec4 localLight;

		R_GlobalPointToLocal(surf->space->modelMatrix, vLight->globalLightOrigin, localLight.ToVec3());
		localLight.w = 0.0f;
		GL_Uniform4fv(offsetof(shaderProgram_t, localLightOrigin), localLight.ToFloatPtr());
	}

	GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 4, GL_FLOAT, false, sizeof(shadowCache_t),
	                       vertexCache.Position(surf->shadowCache));

	// we always draw the sil planes, but we may not need to draw the front or rear caps
	int numIndexes;
	bool external = false;

	if ( !r_useExternalShadows.GetInteger()) {
		numIndexes = surf->numIndexes;
	} else if ( r_useExternalShadows.GetInteger() == 2 ) { // force to no caps for testing
		numIndexes = surf->numShadowIndexesNoCaps;
	} else if ( !( surf->dsFlags & DSF_VIEW_INSIDE_SHADOW )) {
		// if we aren't inside the shadow projection, no caps are ever needed needed
		numIndexes = surf->numShadowIndexesNoCaps;
		external = true;
	} else if ( !vLight->viewInsideLight && !( surf->shadowCapPlaneBits & SHADOW_CAP_INFINITE )) {
		// if we are inside the shadow projection, but outside the light, and drawing
		// a non-infinite shadow, we can skip some caps
		if ( vLight->viewSeesShadowPlaneBits & surf->shadowCapPlaneBits ) {
			// we can see through a rear cap, so we need to draw it, but we can skip the
			// caps on the actual surface
			numIndexes = surf->numShadowIndexesNoFrontCaps;
		} else {
			// we don't need to draw any caps
			numIndexes = surf->numShadowIndexesNoCaps;
		}

		external = true;
	} else {
		// must draw everything
		numIndexes = surf->numIndexes;
	}

	// depth-fail stencil shadows
	if ( !external ) {
		qglStencilOpSeparate(backEnd.viewDef->isMirror ? GL_FRONT : GL_BACK, GL_KEEP, GL_DECR, GL_KEEP);
		qglStencilOpSeparate(backEnd.viewDef->isMirror ? GL_BACK : GL_FRONT, GL_KEEP, GL_INCR, GL_KEEP);
	} else {
		// traditional depth-pass stencil shadows
		qglStencilOpSeparate(backEnd.viewDef->isMirror ? GL_FRONT : GL_BACK, GL_KEEP, GL_KEEP, GL_INCR);
		qglStencilOpSeparate(backEnd.viewDef->isMirror ? GL_BACK : GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR);
	}
	RB_DrawShadowElementsWithCounters(surf, numIndexes);

	// patent-free work around
	/*
	if (!external) {
	  // "preload" the stencil buffer with the number of volumes
	  // that get clipped by the near or far clip plane
	  qglStencilOp(GL_KEEP, GL_DECR, GL_DECR);
	  GL_Cull(CT_FRONT_SIDED);
	  RB_DrawShadowElementsWithCounters(tri, numIndexes);
	  qglStencilOp(GL_KEEP, GL_INCR, GL_INCR);
	  GL_Cull(CT_BACK_SIDED);
	  RB_DrawShadowElementsWithCounters(tri, numIndexes);
	}

	// traditional depth-pass stencil shadows
	qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
	GL_Cull(CT_FRONT_SIDED);
	RB_DrawShadowElementsWithCounters(tri, numIndexes);

	qglStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
	GL_Cull(CT_BACK_SIDED);
	RB_DrawShadowElementsWithCounters(tri, numIndexes);
	*/
}


/*
======================
RB_RenderDrawSurfChainWithFunction
======================
*/
void RB_GLSL_RenderDrawSurfChainWithFunction(const drawSurf_t* drawSurfs,
        void (* triFunc_)(const drawSurf_t*, const viewLight_t*), const viewLight_t* vLight) {
	const drawSurf_t* drawSurf;

	backEnd.currentSpace = NULL;

	for ( drawSurf = drawSurfs; drawSurf; drawSurf = drawSurf->nextOnLight ) {

		// Change the MVP matrix if needed
		if ( drawSurf->space != backEnd.currentSpace ) {
			GL_UniformMatrix4fv(offsetof(shaderProgram_t, modelMatrix), drawSurf->space->modelMatrix);

			glBindBufferBase(
					GL_UNIFORM_BUFFER,
					backEnd.glState.currentProgram->projectionMatrixBinding,
					projectionMatricesBuffer[RB_CalculateProjection(drawSurf)]);
		}

		// Hack Depth Range if necessary
		bool bNeedRestoreDepthRange = false;
		if (drawSurf->space->weaponDepthHack && drawSurf->space->modelDepthHack == 0.0f) {
			qglDepthRangef(0.0f, 0.5f);
			bNeedRestoreDepthRange = true;
		}

		// change the scissor if needed
		if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals(drawSurf->scissorRect)) {
			backEnd.currentScissor = drawSurf->scissorRect;
			if (( backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1 ) < 0.0f ||
			        ( backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ) < 0.0f ) {
				backEnd.currentScissor = backEnd.viewDef->scissor;
			}
			qglScissor(backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
			           backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
			           backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
			           backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1);

		}

		// render it
		triFunc_(drawSurf, vLight);

		// Restore the Depth Range in case it have been hacked
		if ( bNeedRestoreDepthRange ) {
			qglDepthRangef( 0.0f, 1.0f );
		}

		backEnd.currentSpace = drawSurf->space;
	}

	backEnd.currentSpace = NULL;
}

/*
=====================
RB_GLSL_StencilShadowPass

Stencil test should already be enabled, and the stencil buffer should have
been set to 128 on any surfaces that might receive shadows
=====================
*/
void RB_GLSL_StencilShadowPass(const drawSurf_t* drawSurfs, const viewLight_t* vLight) {

	//////////////
	// Skip cases
	//////////////

	if ( !r_shadows.GetBool()) {
		return;
	}

	if ( !drawSurfs ) {
		return;
	}

	//////////////////
	// Setup GL state
	//////////////////

	// Expected client GL state:
	// Vertex attribute enabled
	// Color attribute enabled

	// Use the stencil shadow shader
	GL_UseProgram(&stencilShadowShader);

	glBindBufferBase(
            GL_UNIFORM_BUFFER,
			backEnd.glState.currentProgram->viewMatricesBinding,
			viewMatricesBuffer);

	// Setup attributes arrays
	// Vertex attribute is always enabled
	// Disable Color attribute (as it is enabled by default)
	// Disable TexCoord attribute (as it is enabled by default)
	GL_DisableVertexAttribArray(ATTR_COLOR);
	GL_DisableVertexAttribArray(ATTR_TEXCOORD);

	// don't write to the color buffer, just the stencil buffer
	GL_State(GLS_DEPTHMASK | GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHFUNC_LESS);

	if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat()) {
		qglPolygonOffset(r_shadowPolygonFactor.GetFloat(), -r_shadowPolygonOffset.GetFloat());
		qglEnable(GL_POLYGON_OFFSET_FILL);
	}

	qglStencilFunc(GL_ALWAYS, 1, 255);

	// Culling will be done two side for shadows
	GL_Cull(CT_TWO_SIDED);

	RB_GLSL_RenderDrawSurfChainWithFunction(drawSurfs, RB_T_GLSL_Shadow, vLight);

	// Restore culling
	GL_Cull(CT_FRONT_SIDED);

	if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat()) {
		qglDisable(GL_POLYGON_OFFSET_FILL);
	}

	qglStencilFunc(GL_GEQUAL, 128, 255);
	qglStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);


	// Restore attributes arrays
	// Vertex attribute is always enabled
	// Re-enable Color attribute (as it is enabled by default)
	// Re-enable TexCoord attribute (as it is enabled by default)
	GL_EnableVertexAttribArray(ATTR_COLOR);
	GL_EnableVertexAttribArray(ATTR_TEXCOORD);
}

/*
==================
RB_GLSL_DrawInteractions
==================
*/
void RB_GLSL_DrawInteractions(void) {

	///////////////////////
	// For each light loop
	///////////////////////

	const viewLight_t* vLight;
	//
	// for each light, perform adding and shadowing
	//
	for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) {

		//////////////
		// Skip Cases
		//////////////

		// do fogging later
		if ( vLight->lightShader->IsFogLight()) {
			continue;
		}

		if ( vLight->lightShader->IsBlendLight()) {
			continue;
		}

		if ( !vLight->localInteractions && !vLight->globalInteractions
		        && !vLight->translucentInteractions ) {
			continue;
		}

		//////////////////
		// Setup GL state
		//////////////////

		// clear the stencil buffer if needed
		if ( vLight->globalShadows || vLight->localShadows ) {

			if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals(vLight->scissorRect)) {
				backEnd.currentScissor = vLight->scissorRect;
				if (( backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1 ) < 0.0f ||
				        ( backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ) < 0.0f ) {
					backEnd.currentScissor = backEnd.viewDef->scissor;
				}
				qglScissor(backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
				           backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
				           backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
				           backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1);
			}

			qglClear(GL_STENCIL_BUFFER_BIT);
		} else {
			// no shadows, so no need to read or write the stencil buffer
			// we might in theory want to use GL_ALWAYS instead of disabling
			// completely, to satisfy the invarience rules
			qglStencilFunc(GL_ALWAYS, 128, 255);
		}

		RB_GLSL_StencilShadowPass(vLight->globalShadows, vLight);
		RB_GLSL_CreateDrawInteractions(vLight->localInteractions, vLight);
		RB_GLSL_StencilShadowPass(vLight->localShadows, vLight);
		RB_GLSL_CreateDrawInteractions(vLight->globalInteractions, vLight);

		// translucent surfaces never get stencil shadowed
		if ( r_skipTranslucent.GetBool()) {
			continue;
		}

		qglStencilFunc(GL_ALWAYS, 128, 255);
		RB_GLSL_CreateDrawInteractions(vLight->translucentInteractions, vLight, GLS_DEPTHFUNC_LESS);
	}

	// disable stencil shadow test
	qglStencilFunc(GL_ALWAYS, 128, 255);
}

// NB: oh, a nice global variable. Argh....
static idPlane fogPlanes[4];

/*
=====================
RB_T_BasicFog
=====================
*/
static void RB_T_GLSL_BasicFog(const drawSurf_t* surf, const viewLight_t* vLight) {
	if ( backEnd.currentSpace != surf->space ) {
		idPlane transfoFogPlane[4];

		//S
		R_GlobalPlaneToLocal(surf->space->modelMatrix, fogPlanes[0], transfoFogPlane[0]);
		transfoFogPlane[0][3] += 0.5;
		//T
		transfoFogPlane[1][0] = transfoFogPlane[1][1] = transfoFogPlane[1][2] = 0;
		transfoFogPlane[1][3] = 0.5;
		//T
		R_GlobalPlaneToLocal(surf->space->modelMatrix, fogPlanes[2], transfoFogPlane[2]);
		transfoFogPlane[2][3] += FOG_ENTER;
		//S
		R_GlobalPlaneToLocal(surf->space->modelMatrix, fogPlanes[3], transfoFogPlane[3]);

		idMat4 fogMatrix;
		fogMatrix[0] = transfoFogPlane[0].ToVec4();
		fogMatrix[1] = transfoFogPlane[1].ToVec4();
		fogMatrix[2] = transfoFogPlane[2].ToVec4();
		fogMatrix[3] = transfoFogPlane[3].ToVec4();

		GL_UniformMatrix4fv(offsetof(shaderProgram_t, fogMatrix), fogMatrix.ToFloatPtr());
	}

	idDrawVert* ac = (idDrawVert*) vertexCache.Position(surf->ambientCache);

	GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert),
	                       ac->xyz.ToFloatPtr());

	RB_DrawElementsWithCounters(surf);
}

/*
==================
RB_FogPass
==================
*/
void RB_GLSL_FogPass(const drawSurf_t* drawSurfs, const drawSurf_t* drawSurfs2, const viewLight_t* vLight) {

	drawSurf_t ds;
	const idMaterial* lightShader;
	const shaderStage_t* stage;
	const float* regs;

	// create a surface for the light frustom triangles, which are oriented drawn side out
	const srfTriangles_t* frustumTris = vLight->frustumTris;

	// if we ran out of vertex cache memory, skip it
	if ( !frustumTris->ambientCache ) {
		return;
	}

	// Initial expected GL state:
	// Texture 0 is active, and bound to NULL
	// Vertex attribute array is enabled
	// All other attributes array are disabled
	// No shaders active

	GL_UseProgram(&fogShader);

	glBindBufferBase(
			GL_UNIFORM_BUFFER,
			backEnd.glState.currentProgram->viewMatricesBinding,
			viewMatricesBuffer);

	// Setup attributes arrays
	// Vertex attribute is always enabled
	// Disable Color attribute (as it is enabled by default)
	// Disable TexCoord attribute (as it is enabled by default)
	GL_DisableVertexAttribArray(ATTR_COLOR);
	GL_DisableVertexAttribArray(ATTR_TEXCOORD);

	memset(&ds, 0, sizeof(ds));
	ds.space = &backEnd.viewDef->worldSpace;
	//ds.geo = frustumTris;
	ds.ambientCache = frustumTris->ambientCache;
    ds.indexCache = frustumTris->indexCache;
    ds.shadowCache = frustumTris->shadowCache;
    ds.numIndexes = frustumTris->numIndexes;
	ds.scissorRect = backEnd.viewDef->scissor;

	// find the current color and density of the fog
	lightShader = vLight->lightShader;
	regs = vLight->shaderRegisters;
	// assume fog shaders have only a single stage
	stage = lightShader->GetStage(0);

	float lightColor[4];

	lightColor[0] = regs[stage->color.registers[0]];
	lightColor[1] = regs[stage->color.registers[1]];
	lightColor[2] = regs[stage->color.registers[2]];
	lightColor[3] = regs[stage->color.registers[3]];

	// FogColor
	GL_Uniform4fv(offsetof(shaderProgram_t, fogColor), lightColor);

	// calculate the falloff planes
	const float a = ( lightColor[3] <= 1.0 ) ? -0.5f / DEFAULT_FOG_DISTANCE : -0.5f / lightColor[3];

	// texture 0 is the falloff image
	// It is expected to be already active
	globalImages->fogImage->Bind();

	fogPlanes[0][0] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][2];
	fogPlanes[0][1] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][6];
	fogPlanes[0][2] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][10];
	fogPlanes[0][3] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][14];

	fogPlanes[1][0] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][0];
	fogPlanes[1][1] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][4];
	fogPlanes[1][2] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][8];
	fogPlanes[1][3] = a * backEnd.viewDef->worldSpace.eyeViewMatrix[2][12];

	// texture 1 is the entering plane fade correction
	GL_SelectTexture(1);
	globalImages->fogEnterImage->Bind();
	// reactive texture 0 for next passes
	GL_SelectTexture(0);

	// T will get a texgen for the fade plane, which is always the "top" plane on unrotated lights
	fogPlanes[2][0] = 0.001f * vLight->fogPlane[0];
	fogPlanes[2][1] = 0.001f * vLight->fogPlane[1];
	fogPlanes[2][2] = 0.001f * vLight->fogPlane[2];
	fogPlanes[2][3] = 0.001f * vLight->fogPlane[3];

	// S is based on the view origin
	const float s = backEnd.viewDef->renderView.vieworg * fogPlanes[2].Normal() + fogPlanes[2][3];
	fogPlanes[3][0] = 0;
	fogPlanes[3][1] = 0;
	fogPlanes[3][2] = 0;
	fogPlanes[3][3] = FOG_ENTER + s;

	// draw it
	GL_State(GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL);
	RB_GLSL_RenderDrawSurfChainWithFunction(drawSurfs, RB_T_GLSL_BasicFog, vLight);
	RB_GLSL_RenderDrawSurfChainWithFunction(drawSurfs2, RB_T_GLSL_BasicFog, vLight);

	// the light frustum bounding planes aren't in the depth buffer, so use depthfunc_less instead
	// of depthfunc_equal
	GL_State(GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_LESS);
	GL_Cull(CT_BACK_SIDED);
	RB_GLSL_RenderDrawSurfChainWithFunction(&ds, RB_T_GLSL_BasicFog, vLight);
	// Restore culling
	GL_Cull(CT_FRONT_SIDED);
	GL_State(GLS_DEPTHMASK | GLS_DEPTHFUNC_EQUAL); // Restore DepthFunc

	// Restore attributes arrays
	// Vertex attribute is always enabled
	// Re-enable Color attribute (as it is enabled by default)
	// Re-enable TexCoord attribute (as it is enabled by default)
	GL_EnableVertexAttribArray(ATTR_COLOR);
	GL_EnableVertexAttribArray(ATTR_TEXCOORD);
}

/*
==================
RB_T_FillDepthBuffer
==================
*/
void RB_T_GLSL_FillDepthBuffer(const drawSurf_t* surf) {

	const idMaterial* const shader = surf->material;

	//////////////
	// Skip cases
	//////////////

	if ( !shader->IsDrawn()) {
		return;
	}

	//const srfTriangles_t* const tri = surf->geo;

	// some deforms may disable themselves by setting numIndexes = 0
	if ( !surf->numIndexes ) {
		return;
	}

	// translucent surfaces don't put anything in the depth buffer and don't
	// test against it, which makes them fail the mirror clip plane operation
	if ( shader->Coverage() == MC_TRANSLUCENT ) {
		return;
	}

	if ( !surf->ambientCache ) {
		return;
	}

	// get the expressions for conditionals / color / texcoords
	const float* const regs = surf->shaderRegisters;

	// if all stages of a material have been conditioned off, don't do anything
	int stage;
	for ( stage = 0; stage < shader->GetNumStages(); stage++ ) {
		const shaderStage_t* pStage = shader->GetStage(stage);

		// check the stage enable condition
		if ( regs[pStage->conditionRegister] != 0 ) {
			break;
		}
	}

	if ( stage == shader->GetNumStages()) {
		return;
	}

	///////////////////////////////////////////
	// GL Shader setup for the current surface
	///////////////////////////////////////////

	// update the clip plane if needed
	if ( backEnd.viewDef->numClipPlanes && surf->space != backEnd.currentSpace ) {
		idPlane plane;

		R_GlobalPlaneToLocal(surf->space->modelMatrix, backEnd.viewDef->clipPlanes[0], plane);
		plane[3] += 0.5;  // the notch is in the middle

		GL_Uniform4fv(offsetof(shaderProgram_t, clipPlane), plane.ToFloatPtr());
	}

	// set polygon offset if necessary
	// NB: will be restored at the end of the process
	if ( shader->TestMaterialFlag(MF_POLYGONOFFSET)) {
		qglEnable(GL_POLYGON_OFFSET_FILL);
		qglPolygonOffset(r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset());
	}

	// Color
	// black by default
	float color[4] = { 0, 0, 0, 1 };
	// subviews will just down-modulate the color buffer by overbright
	// NB: will be restored at end of the process
	if ( shader->GetSort() == SS_SUBVIEW ) {
		GL_State(GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO | GLS_DEPTHFUNC_LESS);
		color[0] = color[1] = color[2] = 1.0f; // NB: was 1.0 / backEnd.overBright );
	}
	GL_Uniform4fv(offsetof(shaderProgram_t, glColor), color);

	// Get vertex data
	idDrawVert* ac = (idDrawVert*) vertexCache.Position(surf->ambientCache);

	// Setup attribute pointers
	GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert),
	                       ac->xyz.ToFloatPtr());
	GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_TexCoord), 2, GL_FLOAT, false, sizeof(idDrawVert),
	                       ac->st.ToFloatPtr());

	bool drawSolid = false;

	if ( shader->Coverage() == MC_OPAQUE ) {
		drawSolid = true;
	}

	////////////////////////////////
	// Perforated surfaces handling
	////////////////////////////////

	// we may have multiple alpha tested stages
	if ( shader->Coverage() == MC_PERFORATED ) {
		// if the only alpha tested stages are condition register omitted,
		// draw a normal opaque surface
		bool didDraw = false;

		///////////////////////
		// For each stage loop
		///////////////////////

		// perforated surfaces may have multiple alpha tested stages
		for ( stage = 0; stage < shader->GetNumStages(); stage++ ) {
			const shaderStage_t* pStage = shader->GetStage(stage);

			//////////////
			// Skip cases
			//////////////

			if ( !pStage->hasAlphaTest ) {
				continue;
			}

			// check the stage enable condition
			if ( regs[pStage->conditionRegister] == 0 ) {
				continue;
			}

			// if we at least tried to draw an alpha tested stage,
			// we won't draw the opaque surface
			didDraw = true;

			// set the alpha modulate
			color[3] = regs[pStage->color.registers[3]];

			// skip the entire stage if alpha would be black
			if ( color[3] <= 0 ) {
				continue;
			}

			//////////////////////////
			// GL Setup for the stage
			//////////////////////////

			// Color
			// alpha testing
			GL_Uniform4fv(offsetof(shaderProgram_t, glColor), color);
			GL_Uniform1fv(offsetof(shaderProgram_t, alphaTest), &regs[pStage->alphaTestRegister]);

			// bind the texture
			pStage->texture.image->Bind();

			// Setup the texture matrix if needed
			// NB: will be restored to identity
			if ( pStage->texture.hasMatrix ) {
				float matrix[16];
				RB_GetShaderTextureMatrix(surf->shaderRegisters, &pStage->texture, matrix);
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), matrix);
			}

			///////////
			// Draw it
			///////////
			RB_DrawElementsWithCounters(surf);

			////////////////////////////////////////////////////////////
			// Restore everything to an acceptable state for next stage
			////////////////////////////////////////////////////////////

			// Restore identity matrix
			if ( pStage->texture.hasMatrix ) {
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
			}
		}

		if ( !didDraw ) {
			drawSolid = true;
		}

		///////////////////////////////////////////////////////////
		// Restore everything to an acceptable state for next step
		///////////////////////////////////////////////////////////

		// Restore color alpha to opaque
		color[3] = 1;
		GL_Uniform4fv(offsetof(shaderProgram_t, glColor), color);

		// Restore alphatest always passing
		static const float one[1] = { 1 };
		GL_Uniform1fv(offsetof(shaderProgram_t, alphaTest), one);

		// Restore white image binding to Tex0
		globalImages->whiteImage->Bind();
	}

	////////////////////////////////////////
	// Normal surfaces case (non perforated)
	////////////////////////////////////////

	// draw the entire surface solid
	if ( drawSolid ) {
		///////////
		// Draw it
		///////////
		RB_DrawElementsWithCounters(surf);
	}

	/////////////////////////////////////////////
	// Restore everything to an acceptable state
	/////////////////////////////////////////////

	// reset polygon offset
	if ( shader->TestMaterialFlag(MF_POLYGONOFFSET)) {
		qglDisable(GL_POLYGON_OFFSET_FILL);
	}

	// Restore blending
	if ( shader->GetSort() == SS_SUBVIEW ) {
		GL_State(GLS_DEPTHFUNC_LESS);
	}
}

/*
=====================
RB_GLSL_FillDepthBuffer

If we are rendering a subview with a near clip plane, use a second texture
to force the alpha test to fail when behind that clip plane
=====================
*/
void RB_GLSL_FillDepthBuffer(drawSurf_t** drawSurfs, int numDrawSurfs) {

	//////////////
	// Skip cases
	//////////////

	// if we are just doing 2D rendering, no need to fill the depth buffer
	if ( !backEnd.viewDef->viewEntitys ) {
		return;
	}

	////////////////////////////////////////
	// GL Shader setup for the current pass
	// (ie. common to each surface)
	////////////////////////////////////////

	// Expected client GL State at this point
	// Tex0 active
	// Vertex attribute enabled
	// Color attribute enabled
	// Shader AlphaTest is one
	// Shader Texture Matrix is Identity

	// If clip planes are enabled in the view, use he "Clip" version of zfill shader
	// and enable the second texture for mirror plane clipping if needed
	if ( backEnd.viewDef->numClipPlanes ) {
		// Use he zfillClip shader
		GL_UseProgram(&zfillClipShader);

		// Bind the Texture 1 to alphaNotchImage
		GL_SelectTexture(1);
		globalImages->alphaNotchImage->Bind();

		// Be sure to reactivate Texture 0, as it will be bound right after
		GL_SelectTexture(0);
	}
	// If no clip planes, just use the regular zfill shader
	else {
		GL_UseProgram(&zfillShader);
	}

	glBindBufferBase(
			GL_UNIFORM_BUFFER,
			backEnd.glState.currentProgram->viewMatricesBinding,
			viewMatricesBuffer);

	// Setup attributes arrays
	// Vertex attribute is always enabled
	// TexCoord attribute is always enabled
	// Disable Color attribute (as it is enabled by default)
	GL_DisableVertexAttribArray(ATTR_COLOR);

	// Texture 0 will be used for alpha tested surfaces. It should be already active.
	// Bind it to white image by default
	globalImages->whiteImage->Bind();

	// Decal surfaces may enable polygon offset
	// GAB Note: Looks like it is not needed, because in case of offsetted surface we will use the offset value of the surface
	// qglPolygonOffset(r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat());

	// Depth func to LESS
	GL_State(GLS_DEPTHFUNC_LESS);

	// Enable stencil test if we are going to be using it for shadows.
	// If we didn't do this, it would be legal behavior to get z fighting
	// from the ambient pass and the light passes.
	qglEnable(GL_STENCIL_TEST);
	qglStencilFunc(GL_ALWAYS, 1, 255);

	//////////////////////////
	// For each surfaces loop
	//////////////////////////

	// Optimization to only change MVP matrix when needed
	backEnd.currentSpace = NULL;

	for ( int i = 0; i < numDrawSurfs; i++ ) {

		const drawSurf_t* const drawSurf = drawSurfs[i];

		///////////////////////////////////////////
		// GL shader setup for the current surface
		///////////////////////////////////////////

		// Change the MVP matrix if needed
		if ( drawSurf->space != backEnd.currentSpace ) {
			GL_UniformMatrix4fv(offsetof(shaderProgram_t, modelMatrix), drawSurf->space->modelMatrix);

			glBindBufferBase(
					GL_UNIFORM_BUFFER,
					backEnd.glState.currentProgram->projectionMatrixBinding,
					projectionMatricesBuffer[RB_CalculateProjection(drawSurf)]);
		}

		// Hack Depth Range if necessary
		bool bNeedRestoreDepthRange = false;
		if (drawSurf->space->weaponDepthHack && drawSurf->space->modelDepthHack == 0.0f) {
			qglDepthRangef(0, 0.5);
			bNeedRestoreDepthRange = true;
		}

		// change the scissor if needed
		if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals(drawSurf->scissorRect)) {
			backEnd.currentScissor = drawSurf->scissorRect;
			if (( backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1 ) < 0.0f ||
			        ( backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ) < 0.0f ) {
				backEnd.currentScissor = backEnd.viewDef->scissor;
			}
			qglScissor(backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
			           backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
			           backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
			           backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1);
		}

		////////////////////
		// Do the real work
		////////////////////
		RB_T_GLSL_FillDepthBuffer(drawSurf);

		/////////////////////////////////////////////
		// Restore everything to an acceptable state
		/////////////////////////////////////////////

		if (bNeedRestoreDepthRange) {
			qglDepthRangef(0.0f, 1.0f);
		}

		// Let's change space for next iteration
		backEnd.currentSpace = drawSurf->space;
	}

	/////////////////////////////////////////////
	// Restore everything to an acceptable state
	/////////////////////////////////////////////
	// Restore current space to NULL
	backEnd.currentSpace = NULL;

	// Restore attributes arrays
	// Vertex attribute is always enabled
	// TexCoord attribute is always enabled
	// Re-enable Color attribute (as it is enabled by default)
	GL_EnableVertexAttribArray(ATTR_COLOR);
}

/*
==================
RB_GLSL_T_RenderShaderPasses

This is also called for the generated 2D rendering
==================
*/
void RB_GLSL_T_RenderShaderPasses(const drawSurf_t* surf, GLuint projection) {

	// global constants
	static const GLfloat zero[1] = { 0 };
	static const GLfloat one[1] = { 1 };
	static const GLfloat oneScaled[1] = { 1 / 255.0f };
	static const GLfloat negOneScaled[1] = { -1.0f / 255.0f };

	// usefull pointers
	const idMaterial* const shader = surf->material;
//	const srfTriangles_t* const tri = surf->geo;

	//////////////
	// Skip cases
	//////////////

#ifdef NO_LIGHT
	if ( !r_noLight.GetBool() )
#endif
	if ( !shader->HasAmbient()) {
		return;
	}

	if ( shader->IsPortalSky()) {
		return;
	}

	// some deforms may disable themselves by setting numIndexes = 0
	if ( !surf->numIndexes ) {
		return;
	}

	if ( !surf->ambientCache ) {
		return;
	}


	///////////////////////////////////
	// GL shader setup for the surface
	// (ie. common to each Stage)
	///////////////////////////////////

	// change the scissor if needed
	if ( r_useScissor.GetBool() &&
	    !backEnd.currentScissor.Equals(surf->scissorRect)) {
		backEnd.currentScissor = surf->scissorRect;
		if (( backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1 ) < 0.0f ||
		        ( backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ) < 0.0f ) {
			backEnd.currentScissor = backEnd.viewDef->scissor;
		}
		qglScissor(backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
		           backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
		           backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
		           backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1);

	}

	// set polygon offset if necessary
	// NB: must be restored at end of process
	if ( shader->TestMaterialFlag(MF_POLYGONOFFSET)) {
		qglEnable(GL_POLYGON_OFFSET_FILL);
		qglPolygonOffset(r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset());
	}

	// set face culling appropriately
	GL_Cull(shader->GetCullType());

	// Location of vertex attributes data
	const idDrawVert* const ac = (const idDrawVert* const) vertexCache.Position(surf->ambientCache);

	// get the expressions for conditionals / color / texcoords
	const float* const regs = surf->shaderRegisters;

	// Caches to set per surface shader GL state only when necessary
	bool bMVPSet[TG_GLASSWARP-TG_EXPLICIT];
	memset(bMVPSet, 0, (TG_GLASSWARP-TG_EXPLICIT)*sizeof(bool));
	bool bVASet [TG_GLASSWARP-TG_EXPLICIT];
	memset(bVASet, 0, (TG_GLASSWARP-TG_EXPLICIT)*sizeof(bool));

	// precompute the local view origin (might be needed for some texgens)
	idVec4 localViewOrigin;
	R_GlobalPointToLocal(surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, localViewOrigin.ToVec3());
	localViewOrigin.w = 1.0f;

	///////////////////////
	// For each stage loop
	///////////////////////
	for ( int stage = 0; stage < shader->GetNumStages(); stage++ ) {

		const shaderStage_t* const pStage = shader->GetStage(stage);

		///////////////
		// Skip cases
		///////////////

		// check the enable condition
		if ( regs[pStage->conditionRegister] == 0 ) {
			continue;
		}

		// skip the stages involved in lighting
		if ( pStage->lighting != SL_AMBIENT ) {
			continue;
		}

		// skip if the stage is ( GL_ZERO, GL_ONE ), which is used for some alpha masks
		if (( pStage->drawStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS )) ==
		        ( GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE )) {
			continue;
		}

		// see if we are a new-style stage
		const newShaderStage_t* const newStage = pStage->newStage;

		if ( newStage ) {
			// new style stages: Not implemented in GLSL yet!
			continue;
		} else {

			// old style stages

			/////////////////////////
			// Additional skip cases
			/////////////////////////

			// precompute the color
			const float color[4] = {
				regs[pStage->color.registers[0]],
				regs[pStage->color.registers[1]],
				regs[pStage->color.registers[2]],
				regs[pStage->color.registers[3]]
			};

			// skip the entire stage if an add would be black
			if (
			    ( pStage->drawStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS )) == ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE )
			    && color[0] <= 0 && color[1] <= 0 && color[2] <= 0 ) {
				continue;
			}

			// skip the entire stage if a blend would be completely transparent
			if (( pStage->drawStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS )) ==
			        ( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA )
			        && color[3] <= 0 ) {
				continue;
			}

			/////////////////////////////////
			// GL shader setup for the stage
			/////////////////////////////////
			// The very first thing we need to do before going down into GL is to choose he correct GLSL shader depending on
			// the associated TexGen. Then, setup its specific uniforms/attribs, and then only we can setup the common uniforms/attribs

			if ( pStage->texture.texgen == TG_DIFFUSE_CUBE ) {
				// Not used in game, but implemented because trivial

				// This is diffuse cube mapping
				GL_UseProgram(&diffuseCubeShader);

				glBindBufferBase(
						GL_UNIFORM_BUFFER,
						backEnd.glState.currentProgram->viewMatricesBinding,
						viewMatricesBuffer);

				// Possible that normals should be transformed by a normal matrix in the shader ? I am not sure...

				// Setup texcoord array to use the normals
				GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_TexCoord), 3, GL_FLOAT, false, sizeof(idDrawVert),
				                       ac->normal.ToFloatPtr());

				// Setup the texture matrix
				if ( pStage->texture.hasMatrix ) {
					float matrix[16];
					RB_GetShaderTextureMatrix(surf->shaderRegisters, &pStage->texture, matrix);
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), matrix);
				}
			} else if ( pStage->texture.texgen == TG_SKYBOX_CUBE ) {
				// This is skybox cube mapping
				GL_UseProgram(&skyboxCubeShader);

				glBindBufferBase(
						GL_UNIFORM_BUFFER,
						backEnd.glState.currentProgram->viewMatricesBinding,
						viewMatricesBuffer);

				// Disable TexCoord attribute
				GL_DisableVertexAttribArray(ATTR_TEXCOORD);

				// Setup the local view origin uniform
				GL_Uniform4fv(offsetof(shaderProgram_t, localViewOrigin), localViewOrigin.ToFloatPtr());

				// Setup the texture matrix
				if ( pStage->texture.hasMatrix ) {
					float matrix[16];
					RB_GetShaderTextureMatrix(surf->shaderRegisters, &pStage->texture, matrix);
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), matrix);
				}
			} else if ( pStage->texture.texgen == TG_WOBBLESKY_CUBE ) {
				// This is skybox cube mapping, with special texture matrix
				GL_UseProgram(&skyboxCubeShader);

				glBindBufferBase(
						GL_UNIFORM_BUFFER,
						backEnd.glState.currentProgram->viewMatricesBinding,
						viewMatricesBuffer);

				// Disable TexCoord attribute
				GL_DisableVertexAttribArray(ATTR_TEXCOORD);

				// Setup the local view origin uniform
				GL_Uniform4fv(offsetof(shaderProgram_t, localViewOrigin), localViewOrigin.ToFloatPtr());

				// Setup the texture matrix
				// Note: here, we combine the shader texturematrix and precomputed wobblesky matrix
				if ( pStage->texture.hasMatrix ) {
					float texturematrix[16];
					RB_GetShaderTextureMatrix(surf->shaderRegisters, &pStage->texture, texturematrix);
					float finalmatrix[16];
					myGlMultMatrix(texturematrix, surf->wobbleTransform, finalmatrix);
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), finalmatrix);
				} else {
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), surf->wobbleTransform);
				}
			} else if ( pStage->texture.texgen == TG_SCREEN ) {
				// Not used in game, so not implemented
				continue;
			} else if ( pStage->texture.texgen == TG_SCREEN2 ) {
				// Not used in game, so not implemented
				continue;
			} else if ( pStage->texture.texgen == TG_GLASSWARP ) {
				// Not used in game, so not implemented. The shader code is even not present in original D3 data
				continue;
			} else if ( pStage->texture.texgen == TG_REFLECT_CUBE ) {
				// This is reflection cubemapping
				GL_UseProgram(&reflectionCubeShader);

				glBindBufferBase(
						GL_UNIFORM_BUFFER,
						backEnd.glState.currentProgram->viewMatricesBinding,
						viewMatricesBuffer);

				// NB: in original D3, if the surface had a bump map it would lead to the "Bumpy reflection cubemaping" shader being used.
				// This is not implemented for now, we only do standard reflection cubemaping. Visual difference is really minor.

				// Setup texcoord array to use the normals
				GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_TexCoord), 3, GL_FLOAT, false, sizeof(idDrawVert),
				                       ac->normal.ToFloatPtr());

				// Setup the viewMatrix, we will need it to compute the reflection
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, modelViewMatrix), surf->space->eyeViewMatrix[2]);

				// Setup the texture matrix like original D3 code does: using the transpose viewMatrix of the view
				// NB: this is curious, not sure why this is done like this....
				float mat[16];
				R_TransposeGLMatrix(backEnd.viewDef->worldSpace.eyeViewMatrix[2], mat);
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat);
			} else { // TG_EXPLICIT
				// Otherwise, this is just regular surface shader with explicit texcoords
				GL_UseProgram(&diffuseMapShader);

				glBindBufferBase(
						GL_UNIFORM_BUFFER,
						backEnd.glState.currentProgram->viewMatricesBinding,
						viewMatricesBuffer);

				// Setup the TexCoord pointer
				GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_TexCoord), 2, GL_FLOAT, false, sizeof(idDrawVert),
				                       ac->st.ToFloatPtr());

				// Setup the texture matrix
				if ( pStage->texture.hasMatrix ) {
					float matrix[16];
					RB_GetShaderTextureMatrix(surf->shaderRegisters, &pStage->texture, matrix);
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), matrix);
				}
			}

			// Now we have a shader, we can setup the uniforms and attribute pointers common to all kind of shaders
			// The specifics have already been done in the shader selection code (see above)

			// Non-stage dependent state (per drawsurf, may be done once per GL shader)
			{
				// Vertex Attributes
				if ( !bVASet[pStage->texture.texgen] ) {

					// Setup the Vertex Attrib pointer
					GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert),
					                       ac->xyz.ToFloatPtr());

					// Setup the Color pointer
					GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Color), 4, GL_UNSIGNED_BYTE, false, sizeof(idDrawVert),
					                       (void*) &ac->color);

					bVASet[pStage->texture.texgen] = true;
				}

				// MVP
				if ( !bMVPSet[pStage->texture.texgen] ) {
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, modelMatrix), surf->space->modelMatrix);

					glBindBufferBase(
							GL_UNIFORM_BUFFER,
							backEnd.glState.currentProgram->projectionMatrixBinding,
                            projectionMatricesBuffer[projection]);

					bMVPSet[pStage->texture.texgen] = true;
				}
			}

			// Stage dependent state

			// Setup the Color uniform
			GL_Uniform4fv(offsetof(shaderProgram_t, glColor), color);

			// Setup the Color modulation
			switch ( pStage->vertexColor ) {
			case SVC_MODULATE: {
				GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), oneScaled);
				GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), zero);
				break;
			}
			case SVC_INVERSE_MODULATE: {
				GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), negOneScaled);
				GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), one);
				break;
			}
			default:
			case SVC_IGNORE:
				// This is already the default values (zero, one)
				break;
			}

			// bind the texture (this will be either a dynamic texture, or a static one)
			RB_BindVariableStageImage(&pStage->texture, regs);

			// set the state
			GL_State(pStage->drawStateBits);

			// set privatePolygonOffset if necessary
			if ( pStage->privatePolygonOffset ) {
				qglEnable(GL_POLYGON_OFFSET_FILL);
				qglPolygonOffset(r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * pStage->privatePolygonOffset);
			}

#ifdef NO_LIGHT
			if (r_noLight.GetBool() )
			{
				if (pStage->drawStateBits!=9000)
	                GL_State(pStage->drawStateBits);
	            else
	            {
	                if (shader->TestMaterialFlag(MF_POLYGONOFFSET))
	                    GL_State(GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE|GLS_DEPTHFUNC_LESS);
	                else
	                    GL_State(GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE|GLS_DEPTHFUNC_LESS);
	            }
            }
#endif
			/////////////////////
			// Draw the surface!
			/////////////////////
			RB_DrawElementsWithCounters(surf);

			/////////////////////////////////////////////
			// Restore everything to an acceptable state
			/////////////////////////////////////////////

			// Disable the other attributes array
			if ( pStage->texture.texgen == TG_DIFFUSE_CUBE ) {
				// Restore identity to the texture matrix
				if ( pStage->texture.hasMatrix) {
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
				}
			} else if ( pStage->texture.texgen == TG_SKYBOX_CUBE ) {
				// Reenable TexCoord attribute
				GL_EnableVertexAttribArray(ATTR_TEXCOORD);

				// Restore identity to the texture matrix
				if ( pStage->texture.hasMatrix) {
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
				}
			} else if ( pStage->texture.texgen == TG_WOBBLESKY_CUBE ) {
				// Reenable TexCoord attribute
				GL_EnableVertexAttribArray(ATTR_TEXCOORD);

				// Restore identity to the texture matrix (shall be done each time, as there is the wobblesky transform combined inside)
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
			} else if ( pStage->texture.texgen == TG_SCREEN ) {
			} else if ( pStage->texture.texgen == TG_SCREEN2 ) {
			} else if ( pStage->texture.texgen == TG_GLASSWARP ) {
			} else if ( pStage->texture.texgen == TG_REFLECT_CUBE ) {
				// Restore identity to the texture matrix (shall be done each time)
				GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
			} else {
				// Restore identity to the texture matrix
				if ( pStage->texture.hasMatrix) {
					GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
				}
			}

			// unset privatePolygonOffset if necessary
			if ( pStage->privatePolygonOffset && !surf->material->TestMaterialFlag(MF_POLYGONOFFSET)) {
				qglDisable(GL_POLYGON_OFFSET_FILL);
			} else if ( pStage->privatePolygonOffset && surf->material->TestMaterialFlag(MF_POLYGONOFFSET)) {
				qglPolygonOffset(r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset());
			}

			// Restore color modulation state to default values
			if ( pStage->vertexColor != SVC_IGNORE ) {
				GL_Uniform1fv(offsetof(shaderProgram_t, colorModulate), zero);
				GL_Uniform1fv(offsetof(shaderProgram_t, colorAdd), one);
			}

			// Don't touch the rest, as this will either reset by the next stage, or handled by end of this method
		}
	}

	/////////////////////////////////////////////
	// Restore everything to an acceptable state
	/////////////////////////////////////////////

	// reset polygon offset
	if ( shader->TestMaterialFlag(MF_POLYGONOFFSET)) {
		qglDisable(GL_POLYGON_OFFSET_FILL);
	}
}

/*
=====================
RB_GLSL_DrawShaderPasses

Draw non-light dependent passes
=====================
*/
int RB_GLSL_DrawShaderPasses(drawSurf_t** drawSurfs, int numDrawSurfs) {

	//////////////
	// Skip cases
	//////////////

	// only obey skipAmbient if we are rendering a view
	if ( backEnd.viewDef->viewEntitys && r_skipAmbient.GetBool()) {
		return numDrawSurfs;
	}

	// if we are about to draw the first surface that needs
	// the rendering in a texture, copy it over
	if ( drawSurfs[0]->material->GetSort() >= SS_POST_PROCESS ) {
		if ( r_skipPostProcess.GetBool()) {
			return 0;
		}

		// only dump if in a 3d view
		if ( backEnd.viewDef->viewEntitys ) {
			//globalImages->currentRenderImage->CopyFramebuffer(backEnd.viewDef->viewport.x1,
			//                                                  backEnd.viewDef->viewport.y1,
			//                                                  backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1,
			//                                                  backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1,
			//                                                  true);
		}

		backEnd.currentRenderCopied = true;
	}

	////////////////////////////////////////
	// GL shader setup for the current pass
	// (ie. common to each surface)
	////////////////////////////////////////

	// Texture 0 is expected to be active

	// Setup attributes arrays
	// Vertex attribute is always enabled
	// Color attribute is always enabled
	// Texcoord attribute is always enabled

	/////////////////////////
	// For each surface loop
	/////////////////////////

	GLuint projection = -1;
	backEnd.currentSpace = NULL;

	int i;
	for ( i = 0; i < numDrawSurfs; i++ ) {

		//////////////
		// Skip cases
		//////////////

		if ( drawSurfs[i]->material->SuppressInSubview()) {
			continue;
		}

		if ( backEnd.viewDef->isXraySubview && drawSurfs[i]->space->entityDef ) {
			if ( drawSurfs[i]->space->entityDef->parms.xrayIndex != 2 ) {
				continue;
			}
		}

		// we need to draw the post process shaders after we have drawn the fog lights
		if ( drawSurfs[i]->material->GetSort() >= SS_POST_PROCESS
		        && !backEnd.currentRenderCopied ) {
			break;
		}


		// Change the MVP matrix if needed
		if ( drawSurfs[i]->space != backEnd.currentSpace ) {
			projection = RB_CalculateProjection(drawSurfs[i]);
			// We can't set the uniform now, as we still don't know which shader to use
		}

		// Hack Depth Range if necessary
		bool bNeedRestoreDepthRange = false;
		if (drawSurfs[i]->space->weaponDepthHack && drawSurfs[i]->space->modelDepthHack == 0.0f) {
			qglDepthRangef(0.0f, 0.5f);
			bNeedRestoreDepthRange = true;
		}

		////////////////////
		// Do the real work
		////////////////////
		RB_GLSL_T_RenderShaderPasses(drawSurfs[i], projection);

		if (bNeedRestoreDepthRange) {
			qglDepthRangef(0.0f, 1.0f);
		}

		backEnd.currentSpace = drawSurfs[i]->space;
	}

	/////////////////////////////////////////////
	// Restore everything to an acceptable state
	/////////////////////////////////////////////

	backEnd.currentSpace = NULL;

	// Restore culling
	GL_Cull(CT_FRONT_SIDED);

	// Restore attributes arrays
	// Vertex attribute is always enabled
	// Color attribute is always enabled
	// Texcoord attribute is always enabled

	// Trashed state:
	//   Current Program
	//   Tex0 binding

	// Return the counter of drawn surfaces
	return i;
}

/*
=====================
RB_T_BlendLight

=====================
*/
static void RB_T_GLSL_BlendLight(const drawSurf_t *surf, const viewLight_t* vLight) {
//	const srfTriangles_t *tri = surf->geo;

	////////////
	// GL setup
	////////////

	// Shader uniforms

	// Setup the fogMatrix as being the local Light Projection
	// Only do this once per space
	if (backEnd.currentSpace != surf->space) {
		idPlane lightProject[4];

		int i;
		for (i = 0; i < 4; i++) {
			R_GlobalPlaneToLocal(surf->space->modelMatrix, vLight->lightProject[i], lightProject[i]);
		}

		idMat4 fogMatrix;
		fogMatrix[0] = lightProject[0].ToVec4();
		fogMatrix[1] = lightProject[1].ToVec4();
		fogMatrix[2] = lightProject[2].ToVec4();
		fogMatrix[3] = lightProject[3].ToVec4();
		GL_UniformMatrix4fv(offsetof(shaderProgram_t, fogMatrix), fogMatrix.ToFloatPtr());
	}

	// Attributes pointers

	// This gets used for both blend lights and shadow draws
	if (surf->ambientCache) {
		idDrawVert *ac = (idDrawVert *) vertexCache.Position(surf->ambientCache);
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert), ac->xyz.ToFloatPtr());
	} else if (surf->shadowCache) {
		shadowCache_t *sc = (shadowCache_t *) vertexCache.Position(surf->shadowCache);
		GL_VertexAttribPointer(offsetof(shaderProgram_t, attr_Vertex), 3, GL_FLOAT, false, sizeof(idDrawVert), sc->xyz.ToFloatPtr());
	}

	////////////////////
	// Draw the surface
	////////////////////
	RB_DrawElementsWithCounters(surf);
}

/*
=====================
RB_GLSL BlendLight

Dual texture together the falloff and projection texture with a blend
mode to the framebuffer, instead of interacting with the surface texture
=====================
*/
void RB_GLSL_BlendLight(const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2, const viewLight_t* vLight) {
	const idMaterial * const lightShader = vLight->lightShader;
	const float * const regs = vLight->shaderRegisters;

	//////////////
	// Skip Cases
	//////////////

	if (!drawSurfs) {
		return;
	}

	if (r_skipBlendLights.GetBool()) {
		return;
	}

	////////////////////////////////////
	// GL setup for the current pass
	// (ie. common to all Light Stages)
	////////////////////////////////////

	// Use blendLight shader
	GL_UseProgram(&blendLightShader);

	glBindBufferBase(
			GL_UNIFORM_BUFFER,
			backEnd.glState.currentProgram->viewMatricesBinding,
			viewMatricesBuffer);

	// Texture 1 will get the falloff texture
	GL_SelectTexture(1);
	vLight->falloffImage->Bind();

	// Texture 0 will get the projected texture
	GL_SelectTexture(0);

	////////////////////////
	// For each Light Stage
	////////////////////////

	int i;
	for (i = 0; i < lightShader->GetNumStages(); i++) {
		const shaderStage_t *stage = lightShader->GetStage(i);

		//////////////
		// Skip Cases
		//////////////

		if (!regs[stage->conditionRegister]) {
			continue;
		}

		////////////////////////////////////////
		// GL setup for the current Light Stage
		// (ie. common to all surfaces)
		////////////////////////////////////////

		// Global GL state

		// Setup the drawState
		GL_State(GLS_DEPTHMASK | stage->drawStateBits | GLS_DEPTHFUNC_EQUAL);

		// Bind the projected texture
		stage->texture.image->Bind();

		// Shader Uniforms

		// Setup the texture matrix
		if ( stage->texture.hasMatrix ) {
			float matrix[16];
			RB_GetShaderTextureMatrix(regs, &stage->texture, matrix);
			GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), matrix);
		}

		// Setup the Fog Color
		float lightColor[4];
		lightColor[0] = regs[stage->color.registers[0]];
		lightColor[1] = regs[stage->color.registers[1]];
		lightColor[2] = regs[stage->color.registers[2]];
		lightColor[3] = regs[stage->color.registers[3]];
		GL_Uniform4fv(offsetof(shaderProgram_t, fogColor), lightColor);

		////////////////////
		// Do the Real Work
		////////////////////

		RB_GLSL_RenderDrawSurfChainWithFunction(drawSurfs, RB_T_GLSL_BlendLight, vLight);
		RB_GLSL_RenderDrawSurfChainWithFunction(drawSurfs2, RB_T_GLSL_BlendLight, vLight);

		////////////////////
		// GL state restore
		////////////////////

		// Restore texture matrix to identity
		if (stage->texture.hasMatrix) {
			GL_UniformMatrix4fv(offsetof(shaderProgram_t, textureMatrix), mat4_identity.ToFloatPtr());
		}
	}
}

/*
==================
RB_FogAllLights
==================
*/
void RB_GLSL_FogAllLights(void) {

	//////////////
	// Skip Cases
	//////////////

	if (r_skipFogLights.GetBool() || backEnd.viewDef->isXraySubview /* dont fog in xray mode*/ ) {
		return;
	}

	/////////////////////////////////////////////
	// GL setup for the current pass
	// (ie. common to both fog and blend lights)
	/////////////////////////////////////////////

	// Disable Stencil Test
	qglDisable(GL_STENCIL_TEST);

	// Disable TexCoord array
	// Disable Color array
	GL_DisableVertexAttribArray(ATTR_TEXCOORD);
	GL_DisableVertexAttribArray(ATTR_COLOR);

	//////////////////
	// For each Light
	//////////////////

	const viewLight_t *vLight;
	for (vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next) {

		//////////////
		// Skip Cases
		//////////////

		// We are only interested in Fog and Blend lights
		if (!vLight->lightShader->IsFogLight() && !vLight->lightShader->IsBlendLight()) {
			continue;
		}

		///////////////////////
		// Do the Light passes
		///////////////////////

		if (vLight->lightShader->IsFogLight()) {
			RB_GLSL_FogPass(vLight->globalInteractions, vLight->localInteractions, vLight);
		} else if (vLight->lightShader->IsBlendLight()) {
			RB_GLSL_BlendLight(vLight->globalInteractions, vLight->localInteractions, vLight);
		}
	}

	////////////////////
	// GL state restore
	////////////////////

	// Re-enable TexCoord array
	// Re-enable Color array
	GL_EnableVertexAttribArray(ATTR_TEXCOORD);
	GL_EnableVertexAttribArray(ATTR_COLOR);

	// Re-enable Stencil Test
	qglEnable(GL_STENCIL_TEST);
}


/*
=================
RB_BeginGLSLShaderPasses
=================
*/
void RB_GLSL_PrepareShaders(void) {

	// No shaders set by default
	GL_UseProgram(NULL);

	//Set up the buffers that won't change this frame
	GL_ViewMatricesUniformBuffer(backEnd.viewDef->worldSpace.viewMatrix);

	static bool first = true;
	static float defaultProjection[16];
	if (first) {
        memset(defaultProjection, 0, 16 * sizeof(float));
        first = false;
    }

    //We only need to do the following if the default projection changes
	if (memcmp(defaultProjection, backEnd.viewDef->projectionMatrix, 16 * sizeof(float)) != 0)
    {
	    //Take a copy of the default projection
        memcpy(defaultProjection, backEnd.viewDef->projectionMatrix, 16 * sizeof(float));

        float orthoProjectionMatrix[16];
        memset(orthoProjectionMatrix, 0, sizeof(orthoProjectionMatrix));
        orthoProjectionMatrix[0] = 2.0f / 640.0f;
        orthoProjectionMatrix[5] = -2.0f / 480.0f;
        orthoProjectionMatrix[10] = -2.0f / 1.0f;
        orthoProjectionMatrix[12] = -1.0f;
        orthoProjectionMatrix[13] = 1.0f;
        orthoProjectionMatrix[14] = -1.0f;
        orthoProjectionMatrix[15] = 1.0f;

        //0 is ortho projection matrix
        GL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[ORTHO_PROJECTION],
                                           orthoProjectionMatrix);

        //1 is unadjusted projection matrix
        GL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[NORMAL_PROJECTION],
                                           backEnd.viewDef->projectionMatrix);

        //2 is weapon depth hack projection
        float projection[16];
        RB_ComputeProjection(true, 0.0, projection);
        GL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[WEAPON_PROJECTION], projection);

        //3+ ore model depth hack projections
        for (int i = 0; i <= NUM_DEPTH_HACK_PROJECTIONS; ++i) {
            float depthHack = (float)(i+1) / float(NUM_DEPTH_HACK_PROJECTIONS+1);
            RB_ComputeProjection(false, depthHack, projection);
            GL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[DEPTH_HACK_PROJECTION + i],
                                               projection);
        }

        projectionMatricesSet = true;
    }

	// Always enable the vertex, color and texcoord attributes arrays
	GL_EnableVertexAttribArray(ATTR_VERTEX);
	GL_EnableVertexAttribArray(ATTR_COLOR);
	GL_EnableVertexAttribArray(ATTR_TEXCOORD);
	// Disable the other arrays
	GL_DisableVertexAttribArray(ATTR_NORMAL);
	GL_DisableVertexAttribArray(ATTR_TANGENT);
	GL_DisableVertexAttribArray(ATTR_BITANGENT);
}