[quake3-commits] r2329 - in trunk: . code code/rend2

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Thu Oct 25 21:23:06 EDT 2012


Author: smiletheory
Date: 2012-10-25 21:23:06 -0400 (Thu, 25 Oct 2012)
New Revision: 2329

Added:
   trunk/code/rend2/
   trunk/code/rend2/qgl.h
   trunk/code/rend2/tr_animation.c
   trunk/code/rend2/tr_backend.c
   trunk/code/rend2/tr_bsp.c
   trunk/code/rend2/tr_cmds.c
   trunk/code/rend2/tr_curve.c
   trunk/code/rend2/tr_extensions.c
   trunk/code/rend2/tr_extramath.c
   trunk/code/rend2/tr_extramath.h
   trunk/code/rend2/tr_extratypes.h
   trunk/code/rend2/tr_fbo.c
   trunk/code/rend2/tr_fbo.h
   trunk/code/rend2/tr_flares.c
   trunk/code/rend2/tr_font.c
   trunk/code/rend2/tr_glsl.c
   trunk/code/rend2/tr_image.c
   trunk/code/rend2/tr_image_bmp.c
   trunk/code/rend2/tr_image_jpg.c
   trunk/code/rend2/tr_image_pcx.c
   trunk/code/rend2/tr_image_png.c
   trunk/code/rend2/tr_image_tga.c
   trunk/code/rend2/tr_init.c
   trunk/code/rend2/tr_light.c
   trunk/code/rend2/tr_local.h
   trunk/code/rend2/tr_main.c
   trunk/code/rend2/tr_marks.c
   trunk/code/rend2/tr_mesh.c
   trunk/code/rend2/tr_model.c
   trunk/code/rend2/tr_model_iqm.c
   trunk/code/rend2/tr_noise.c
   trunk/code/rend2/tr_postprocess.c
   trunk/code/rend2/tr_postprocess.h
   trunk/code/rend2/tr_scene.c
   trunk/code/rend2/tr_shade.c
   trunk/code/rend2/tr_shade_calc.c
   trunk/code/rend2/tr_shader.c
   trunk/code/rend2/tr_shadows.c
   trunk/code/rend2/tr_sky.c
   trunk/code/rend2/tr_subs.c
   trunk/code/rend2/tr_surface.c
   trunk/code/rend2/tr_vbo.c
   trunk/code/rend2/tr_world.c
   trunk/rend2-readme.txt
Modified:
   trunk/Makefile
Log:
Added Rend2, an alternate renderer. (Bug #4358)

Modified: trunk/Makefile
===================================================================
--- trunk/Makefile	2012-10-17 21:20:29 UTC (rev 2328)
+++ trunk/Makefile	2012-10-26 01:23:06 UTC (rev 2329)
@@ -50,6 +50,9 @@
 ifndef BUILD_MISSIONPACK
   BUILD_MISSIONPACK=
 endif
+ifndef BUILD_RENDERER_REND2
+  BUILD_RENDERER_REND2=
+endif
 
 ifneq ($(PLATFORM),darwin)
   BUILD_CLIENT_SMP = 0
@@ -216,6 +219,7 @@
 CDIR=$(MOUNT_DIR)/client
 SDIR=$(MOUNT_DIR)/server
 RDIR=$(MOUNT_DIR)/renderer
+R2DIR=$(MOUNT_DIR)/rend2
 CMDIR=$(MOUNT_DIR)/qcommon
 SDLDIR=$(MOUNT_DIR)/sdl
 ASMDIR=$(MOUNT_DIR)/asm
@@ -865,6 +869,12 @@
     ifneq ($(BUILD_CLIENT_SMP),0)
       TARGETS += $(B)/renderer_opengl1_smp_$(SHLIBNAME)
     endif
+    ifneq ($(BUILD_RENDERER_REND2), 0)
+      TARGETS += $(B)/renderer_rend2_$(SHLIBNAME)
+      ifneq ($(BUILD_CLIENT_SMP),0)
+        TARGETS += $(B)/renderer_rend2_smp_$(SHLIBNAME)
+      endif
+    endif
   else
     TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
     ifneq ($(BUILD_CLIENT_SMP),0)
@@ -1173,6 +1183,7 @@
 	@if [ ! -d $(B) ];then $(MKDIR) $(B);fi
 	@if [ ! -d $(B)/client ];then $(MKDIR) $(B)/client;fi
 	@if [ ! -d $(B)/renderer ];then $(MKDIR) $(B)/renderer;fi
+	@if [ ! -d $(B)/rend2 ];then $(MKDIR) $(B)/rend2;fi
 	@if [ ! -d $(B)/renderersmp ];then $(MKDIR) $(B)/renderersmp;fi
 	@if [ ! -d $(B)/ded ];then $(MKDIR) $(B)/ded;fi
 	@if [ ! -d $(B)/$(BASEGAME) ];then $(MKDIR) $(B)/$(BASEGAME);fi
@@ -1478,6 +1489,46 @@
 	$(B)/client/con_tty.o
 endif
 
+Q3R2OBJ = \
+  $(B)/rend2/tr_animation.o \
+  $(B)/rend2/tr_backend.o \
+  $(B)/rend2/tr_bsp.o \
+  $(B)/rend2/tr_cmds.o \
+  $(B)/rend2/tr_curve.o \
+  $(B)/rend2/tr_extramath.o \
+  $(B)/rend2/tr_extensions.o \
+  $(B)/rend2/tr_fbo.o \
+  $(B)/rend2/tr_flares.o \
+  $(B)/rend2/tr_font.o \
+  $(B)/rend2/tr_glsl.o \
+  $(B)/rend2/tr_image.o \
+  $(B)/rend2/tr_image_png.o \
+  $(B)/rend2/tr_image_jpg.o \
+  $(B)/rend2/tr_image_bmp.o \
+  $(B)/rend2/tr_image_tga.o \
+  $(B)/rend2/tr_image_pcx.o \
+  $(B)/rend2/tr_init.o \
+  $(B)/rend2/tr_light.o \
+  $(B)/rend2/tr_main.o \
+  $(B)/rend2/tr_marks.o \
+  $(B)/rend2/tr_mesh.o \
+  $(B)/rend2/tr_model.o \
+  $(B)/rend2/tr_model_iqm.o \
+  $(B)/rend2/tr_noise.o \
+  $(B)/rend2/tr_postprocess.o \
+  $(B)/rend2/tr_scene.o \
+  $(B)/rend2/tr_shade.o \
+  $(B)/rend2/tr_shade_calc.o \
+  $(B)/rend2/tr_shader.o \
+  $(B)/rend2/tr_shadows.o \
+  $(B)/rend2/tr_sky.o \
+  $(B)/rend2/tr_surface.o \
+  $(B)/rend2/tr_vbo.o \
+  $(B)/rend2/tr_world.o \
+  \
+  $(B)/renderer/sdl_gamma.o \
+  $(B)/renderer/sdl_glimp.o
+
 Q3ROBJ = \
   $(B)/renderer/tr_animation.o \
   $(B)/renderer/tr_backend.o \
@@ -1509,18 +1560,25 @@
   $(B)/renderer/tr_surface.o \
   $(B)/renderer/tr_world.o \
   \
-  $(B)/renderer/sdl_gamma.o
-  
+  $(B)/renderer/sdl_gamma.o \
+  $(B)/renderer/sdl_glimp.o
+
 ifneq ($(USE_RENDERER_DLOPEN), 0)
   Q3ROBJ += \
     $(B)/renderer/q_shared.o \
     $(B)/renderer/puff.o \
     $(B)/renderer/q_math.o \
     $(B)/renderer/tr_subs.o
+
+  Q3R2OBJ += \
+    $(B)/renderer/q_shared.o \
+    $(B)/renderer/puff.o \
+    $(B)/renderer/q_math.o \
+    $(B)/renderer/tr_subs.o
 endif
 
 ifneq ($(USE_INTERNAL_JPEG),0)
-  Q3ROBJ += \
+  JPGOBJ = \
     $(B)/renderer/jaricom.o \
     $(B)/renderer/jcapimin.o \
     $(B)/renderer/jcapistd.o \
@@ -1724,12 +1782,6 @@
     $(B)/client/libmumblelink.o
 endif
 
-Q3POBJ += \
-  $(B)/renderer/sdl_glimp.o
-
-Q3POBJ_SMP += \
-  $(B)/renderersmp/sdl_glimp.o
-
 ifneq ($(USE_RENDERER_DLOPEN),0)
 $(B)/$(CLIENTBIN)$(FULLBINEXT): $(Q3OBJ) $(LIBSDLMAIN)
 	$(echo_cmd) "LD $@"
@@ -1737,26 +1789,37 @@
 		-o $@ $(Q3OBJ) \
 		$(LIBSDLMAIN) $(CLIENT_LIBS) $(LIBS)
 
-$(B)/renderer_opengl1_$(SHLIBNAME): $(Q3ROBJ) $(Q3POBJ)
+$(B)/renderer_opengl1_$(SHLIBNAME): $(Q3ROBJ) $(JPGOBJ)
 	$(echo_cmd) "LD $@"
-	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(Q3POBJ) \
+	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(JPGOBJ) \
 		$(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS)
 
-$(B)/renderer_opengl1_smp_$(SHLIBNAME): $(Q3ROBJ) $(Q3POBJ_SMP)
+$(B)/renderer_opengl1_smp_$(SHLIBNAME): $(Q3ROBJ) $(JPGOBJ)
 	$(echo_cmd) "LD $@"
-	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(Q3POBJ_SMP) \
+	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(JPGOBJ) \
 		$(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS)
+
+$(B)/renderer_rend2_$(SHLIBNAME): $(Q3R2OBJ) $(JPGOBJ)
+	$(echo_cmd) "LD $@"
+	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3R2OBJ) $(JPGOBJ) \
+		$(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS)
+
+$(B)/renderer_rend2_smp_$(SHLIBNAME): $(Q3R2OBJ) $(JPGOBJ)
+	$(echo_cmd) "LD $@"
+	$(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3R2OBJ) $(JPGOBJ) \
+		$(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS)
+
 else
-$(B)/$(CLIENTBIN)$(FULLBINEXT): $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ) $(LIBSDLMAIN)
+$(B)/$(CLIENTBIN)$(FULLBINEXT): $(Q3OBJ) $(Q3R2OBJ) $(JPGOBJ) $(LIBSDLMAIN)
 	$(echo_cmd) "LD $@"
 	$(Q)$(CC) $(CLIENT_CFLAGS) $(CFLAGS) $(CLIENT_LDFLAGS) $(LDFLAGS) \
-		-o $@ $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ) \
+		-o $@ $(Q3OBJ) $(Q3R2OBJ) $(JPGOBJ) \
 		$(LIBSDLMAIN) $(CLIENT_LIBS) $(RENDERER_LIBS) $(LIBS)
 
-$(B)/$(CLIENTBIN)-smp$(FULLBINEXT): $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ_SMP) $(LIBSDLMAIN)
+$(B)/$(CLIENTBIN)-smp$(FULLBINEXT): $(Q3OBJ) $(Q3R2OBJ) $(JPGOBJ) $(LIBSDLMAIN)
 	$(echo_cmd) "LD $@"
 	$(Q)$(CC) $(CLIENT_CFLAGS) $(CFLAGS) $(CLIENT_LDFLAGS) $(LDFLAGS) $(THREAD_LDFLAGS) \
-		-o $@ $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ_SMP) \
+		-o $@ $(Q3OBJ) $(Q3R2OBJ) $(JPGOBJ) \
 		$(THREAD_LIBS) $(LIBSDLMAIN) $(CLIENT_LIBS) $(RENDERER_LIBS) $(LIBS)
 endif
 
@@ -2295,6 +2358,9 @@
 
 $(B)/renderer/%.o: $(RDIR)/%.c
 	$(DO_REF_CC)
+	
+$(B)/rend2/%.o: $(R2DIR)/%.c
+	$(DO_REF_CC)
 
 
 $(B)/ded/%.o: $(ASMDIR)/%.s
@@ -2420,7 +2486,7 @@
 # MISC
 #############################################################################
 
-OBJ = $(Q3OBJ) $(Q3POBJ) $(Q3POBJ_SMP) $(Q3ROBJ) $(Q3DOBJ) \
+OBJ = $(Q3OBJ) $(Q3ROBJ) $(Q3R2OBJ) $(Q3DOBJ) $(JPGOBJ) \
   $(MPGOBJ) $(Q3GOBJ) $(Q3CGOBJ) $(MPCGOBJ) $(Q3UIOBJ) $(MPUIOBJ) \
   $(MPGVMOBJ) $(Q3GVMOBJ) $(Q3CGVMOBJ) $(MPCGVMOBJ) $(Q3UIVMOBJ) $(MPUIVMOBJ)
 TOOLSOBJ = $(LBURGOBJ) $(Q3CPPOBJ) $(Q3RCCOBJ) $(Q3LCCOBJ) $(Q3ASMOBJ)
@@ -2441,6 +2507,9 @@
 	$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
   ifneq ($(USE_RENDERER_DLOPEN),0)
 	$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl1_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl1_$(SHLIBNAME)
+    ifneq ($(BUILD_RENDERER_REND2),0)
+	$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_rend2_$(SHLIBNAME) $(COPYBINDIR)/renderer_rend2_$(SHLIBNAME)
+    endif
   endif
 endif
 

Added: trunk/code/rend2/qgl.h
===================================================================
--- trunk/code/rend2/qgl.h	                        (rev 0)
+++ trunk/code/rend2/qgl.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,755 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+/*
+** QGL.H
+*/
+
+#ifndef __QGL_H__
+#define __QGL_H__
+
+#ifdef USE_LOCAL_HEADERS
+#	include "SDL_opengl.h"
+#else
+#	include <SDL_opengl.h>
+#endif
+
+extern void (APIENTRYP qglActiveTextureARB) (GLenum texture);
+extern void (APIENTRYP qglClientActiveTextureARB) (GLenum texture);
+extern void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t);
+
+extern void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count);
+extern void (APIENTRYP qglUnlockArraysEXT) (void);
+
+// GL_EXT_draw_range_elements
+extern void     (APIENTRY * qglDrawRangeElementsEXT) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices);
+
+// GL_EXT_multi_draw_arrays
+extern void     (APIENTRY * qglMultiDrawArraysEXT) (GLenum, GLint *, GLsizei *, GLsizei);
+extern void     (APIENTRY * qglMultiDrawElementsEXT) (GLenum, const GLsizei *, GLenum, const GLvoid **, GLsizei);
+
+// GL_ARB_shading_language_100
+#ifndef GL_ARB_shading_language_100
+#define GL_ARB_shading_language_100
+#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C
+#endif
+
+// GL_ARB_vertex_program
+extern void     (APIENTRY * qglVertexAttrib4fARB) (GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
+extern void     (APIENTRY * qglVertexAttrib4fvARB) (GLuint, const GLfloat *);
+extern void     (APIENTRY * qglVertexAttribPointerARB) (GLuint index, GLint size, GLenum type, GLboolean normalized,
+														GLsizei stride, const GLvoid * pointer);
+extern void     (APIENTRY * qglEnableVertexAttribArrayARB) (GLuint index);
+extern void     (APIENTRY * qglDisableVertexAttribArrayARB) (GLuint index);
+
+// GL_ARB_vertex_buffer_object
+extern void     (APIENTRY * qglBindBufferARB) (GLenum target, GLuint buffer);
+extern void     (APIENTRY * qglDeleteBuffersARB) (GLsizei n, const GLuint * buffers);
+extern void     (APIENTRY * qglGenBuffersARB) (GLsizei n, GLuint * buffers);
+extern          GLboolean(APIENTRY * qglIsBufferARB) (GLuint buffer);
+extern void     (APIENTRY * qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage);
+extern void     (APIENTRY * qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid * data);
+extern void     (APIENTRY * qglGetBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid * data);
+extern void     (APIENTRY * qglGetBufferParameterivARB) (GLenum target, GLenum pname, GLint * params);
+extern void     (APIENTRY * qglGetBufferPointervARB) (GLenum target, GLenum pname, GLvoid * *params);
+
+// GL_ARB_shader_objects
+extern void     (APIENTRY * qglDeleteObjectARB) (GLhandleARB obj);
+extern          GLhandleARB(APIENTRY * qglGetHandleARB) (GLenum pname);
+extern void     (APIENTRY * qglDetachObjectARB) (GLhandleARB containerObj, GLhandleARB attachedObj);
+extern          GLhandleARB(APIENTRY * qglCreateShaderObjectARB) (GLenum shaderType);
+extern void     (APIENTRY * qglShaderSourceARB) (GLhandleARB shaderObj, GLsizei count, const GLcharARB * *string,
+												 const GLint * length);
+extern void     (APIENTRY * qglCompileShaderARB) (GLhandleARB shaderObj);
+extern          GLhandleARB(APIENTRY * qglCreateProgramObjectARB) (void);
+extern void     (APIENTRY * qglAttachObjectARB) (GLhandleARB containerObj, GLhandleARB obj);
+extern void     (APIENTRY * qglLinkProgramARB) (GLhandleARB programObj);
+extern void     (APIENTRY * qglUseProgramObjectARB) (GLhandleARB programObj);
+extern void     (APIENTRY * qglValidateProgramARB) (GLhandleARB programObj);
+extern void     (APIENTRY * qglUniform1fARB) (GLint location, GLfloat v0);
+extern void     (APIENTRY * qglUniform2fARB) (GLint location, GLfloat v0, GLfloat v1);
+extern void     (APIENTRY * qglUniform3fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
+extern void     (APIENTRY * qglUniform4fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+extern void     (APIENTRY * qglUniform1iARB) (GLint location, GLint v0);
+extern void     (APIENTRY * qglUniform2iARB) (GLint location, GLint v0, GLint v1);
+extern void     (APIENTRY * qglUniform3iARB) (GLint location, GLint v0, GLint v1, GLint v2);
+extern void     (APIENTRY * qglUniform4iARB) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
+extern void     (APIENTRY * qglUniform1fvARB) (GLint location, GLsizei count, const GLfloat * value);
+extern void     (APIENTRY * qglUniform2fvARB) (GLint location, GLsizei count, const GLfloat * value);
+extern void     (APIENTRY * qglUniform3fvARB) (GLint location, GLsizei count, const GLfloat * value);
+extern void     (APIENTRY * qglUniform4fvARB) (GLint location, GLsizei count, const GLfloat * value);
+extern void     (APIENTRY * qglUniform2ivARB) (GLint location, GLsizei count, const GLint * value);
+extern void     (APIENTRY * qglUniform3ivARB) (GLint location, GLsizei count, const GLint * value);
+extern void     (APIENTRY * qglUniform4ivARB) (GLint location, GLsizei count, const GLint * value);
+extern void     (APIENTRY * qglUniformMatrix2fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+extern void     (APIENTRY * qglUniformMatrix3fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+extern void     (APIENTRY * qglUniformMatrix4fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+extern void     (APIENTRY * qglGetObjectParameterfvARB) (GLhandleARB obj, GLenum pname, GLfloat * params);
+extern void     (APIENTRY * qglGetObjectParameterivARB) (GLhandleARB obj, GLenum pname, GLint * params);
+extern void     (APIENTRY * qglGetInfoLogARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog);
+extern void     (APIENTRY * qglGetAttachedObjectsARB) (GLhandleARB containerObj, GLsizei maxCount, GLsizei * count,
+													   GLhandleARB * obj);
+extern          GLint(APIENTRY * qglGetUniformLocationARB) (GLhandleARB programObj, const GLcharARB * name);
+extern void     (APIENTRY * qglGetActiveUniformARB) (GLhandleARB programObj, GLuint index, GLsizei maxIndex, GLsizei * length,
+													 GLint * size, GLenum * type, GLcharARB * name);
+extern void     (APIENTRY * qglGetUniformfvARB) (GLhandleARB programObj, GLint location, GLfloat * params);
+extern void     (APIENTRY * qglGetUniformivARB) (GLhandleARB programObj, GLint location, GLint * params);
+extern void     (APIENTRY * qglGetShaderSourceARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * source);
+
+// GL_ARB_vertex_shader
+extern void     (APIENTRY * qglBindAttribLocationARB) (GLhandleARB programObj, GLuint index, const GLcharARB * name);
+extern void     (APIENTRY * qglGetActiveAttribARB) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei * length,
+													GLint * size, GLenum * type, GLcharARB * name);
+extern          GLint(APIENTRY * qglGetAttribLocationARB) (GLhandleARB programObj, const GLcharARB * name);
+
+// GL_ARB_texture_compression
+extern void (APIENTRY * qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, 
+	GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
+	GLint border, GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
+	GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
+	GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
+	GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, 
+	GLsizei imageSize, const GLvoid *data);
+extern void (APIENTRY * qglGetCompressedTexImageARB)(GLenum target, GLint lod,
+	GLvoid *img);
+
+// GL_NVX_gpu_memory_info
+#ifndef GL_NVX_gpu_memory_info
+#define GL_NVX_gpu_memory_info
+#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX          0x9047
+#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX    0x9048
+#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX  0x9049
+#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX            0x904A
+#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX            0x904B
+#endif
+
+// GL_ATI_meminfo
+#ifndef GL_ATI_meminfo
+#define GL_ATI_meminfo
+#define GL_VBO_FREE_MEMORY_ATI                    0x87FB
+#define GL_TEXTURE_FREE_MEMORY_ATI                0x87FC
+#define GL_RENDERBUFFER_FREE_MEMORY_ATI           0x87FD
+#endif
+
+// GL_ARB_texture_float
+#ifndef GL_ARB_texture_float
+#define GL_ARB_texture_float
+#define GL_TEXTURE_RED_TYPE_ARB             0x8C10
+#define GL_TEXTURE_GREEN_TYPE_ARB           0x8C11
+#define GL_TEXTURE_BLUE_TYPE_ARB            0x8C12
+#define GL_TEXTURE_ALPHA_TYPE_ARB           0x8C13
+#define GL_TEXTURE_LUMINANCE_TYPE_ARB       0x8C14
+#define GL_TEXTURE_INTENSITY_TYPE_ARB       0x8C15
+#define GL_TEXTURE_DEPTH_TYPE_ARB           0x8C16
+#define GL_UNSIGNED_NORMALIZED_ARB          0x8C17
+#define GL_RGBA32F_ARB                      0x8814
+#define GL_RGB32F_ARB                       0x8815
+#define GL_ALPHA32F_ARB                     0x8816
+#define GL_INTENSITY32F_ARB                 0x8817
+#define GL_LUMINANCE32F_ARB                 0x8818
+#define GL_LUMINANCE_ALPHA32F_ARB           0x8819
+#define GL_RGBA16F_ARB                      0x881A
+#define GL_RGB16F_ARB                       0x881B
+#define GL_ALPHA16F_ARB                     0x881C
+#define GL_INTENSITY16F_ARB                 0x881D
+#define GL_LUMINANCE16F_ARB                 0x881E
+#define GL_LUMINANCE_ALPHA16F_ARB           0x881F
+#endif
+
+#ifndef GL_ARB_half_float_pixel
+#define GL_ARB_half_float_pixel
+#define GL_HALF_FLOAT_ARB                   0x140B
+#endif
+
+// GL_EXT_framebuffer_object
+extern GLboolean (APIENTRY * qglIsRenderbufferEXT)(GLuint renderbuffer);
+extern void (APIENTRY * qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer);
+extern void (APIENTRY * qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers);
+extern void (APIENTRY * qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers);
+extern void (APIENTRY * qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
+extern void (APIENTRY * qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params);
+extern GLboolean (APIENTRY * qglIsFramebufferEXT)(GLuint framebuffer);
+extern void (APIENTRY * qglBindFramebufferEXT)(GLenum target, GLuint framebuffer);
+extern void (APIENTRY * qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers);
+extern void (APIENTRY * qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers);
+extern GLenum (APIENTRY * qglCheckFramebufferStatusEXT)(GLenum target);
+extern void (APIENTRY * qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level);
+extern void (APIENTRY * qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level);
+extern void (APIENTRY * qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level, GLint zoffset);
+extern void (APIENTRY * qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget,
+	GLuint renderbuffer);
+extern void (APIENTRY * qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params);
+extern void (APIENTRY * qglGenerateMipmapEXT)(GLenum target);
+
+#ifndef GL_EXT_framebuffer_object
+#define GL_EXT_framebuffer_object
+#define GL_FRAMEBUFFER_EXT                     0x8D40
+#define GL_RENDERBUFFER_EXT                    0x8D41
+#define GL_STENCIL_INDEX1_EXT                  0x8D46
+#define GL_STENCIL_INDEX4_EXT                  0x8D47
+#define GL_STENCIL_INDEX8_EXT                  0x8D48
+#define GL_STENCIL_INDEX16_EXT                 0x8D49
+#define GL_RENDERBUFFER_WIDTH_EXT              0x8D42
+#define GL_RENDERBUFFER_HEIGHT_EXT             0x8D43
+#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT    0x8D44
+#define GL_RENDERBUFFER_RED_SIZE_EXT           0x8D50
+#define GL_RENDERBUFFER_GREEN_SIZE_EXT         0x8D51
+#define GL_RENDERBUFFER_BLUE_SIZE_EXT          0x8D52
+#define GL_RENDERBUFFER_ALPHA_SIZE_EXT         0x8D53
+#define GL_RENDERBUFFER_DEPTH_SIZE_EXT         0x8D54
+#define GL_RENDERBUFFER_STENCIL_SIZE_EXT       0x8D55
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT            0x8CD0
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT            0x8CD1
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT          0x8CD2
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT  0x8CD3
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT     0x8CD4
+#define GL_COLOR_ATTACHMENT0_EXT                0x8CE0
+#define GL_COLOR_ATTACHMENT1_EXT                0x8CE1
+#define GL_COLOR_ATTACHMENT2_EXT                0x8CE2
+#define GL_COLOR_ATTACHMENT3_EXT                0x8CE3
+#define GL_COLOR_ATTACHMENT4_EXT                0x8CE4
+#define GL_COLOR_ATTACHMENT5_EXT                0x8CE5
+#define GL_COLOR_ATTACHMENT6_EXT                0x8CE6
+#define GL_COLOR_ATTACHMENT7_EXT                0x8CE7
+#define GL_COLOR_ATTACHMENT8_EXT                0x8CE8
+#define GL_COLOR_ATTACHMENT9_EXT                0x8CE9
+#define GL_COLOR_ATTACHMENT10_EXT               0x8CEA
+#define GL_COLOR_ATTACHMENT11_EXT               0x8CEB
+#define GL_COLOR_ATTACHMENT12_EXT               0x8CEC
+#define GL_COLOR_ATTACHMENT13_EXT               0x8CED
+#define GL_COLOR_ATTACHMENT14_EXT               0x8CEE
+#define GL_COLOR_ATTACHMENT15_EXT               0x8CEF
+#define GL_DEPTH_ATTACHMENT_EXT                 0x8D00
+#define GL_STENCIL_ATTACHMENT_EXT               0x8D20
+#define GL_FRAMEBUFFER_COMPLETE_EXT                          0x8CD5
+#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT             0x8CD6
+#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT     0x8CD7
+#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT             0x8CD9
+#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT                0x8CDA
+#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT            0x8CDB
+#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT            0x8CDC
+#define GL_FRAMEBUFFER_UNSUPPORTED_EXT                       0x8CDD
+#define GL_FRAMEBUFFER_BINDING_EXT             0x8CA6
+#define GL_RENDERBUFFER_BINDING_EXT            0x8CA7
+#define GL_MAX_COLOR_ATTACHMENTS_EXT           0x8CDF
+#define GL_MAX_RENDERBUFFER_SIZE_EXT           0x84E8
+#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT   0x0506
+#endif
+
+// GL_EXT_packed_depth_stencil
+#ifndef GL_EXT_packed_depth_stencil
+#define GL_EXT_packed_depth_stencil
+#define GL_DEPTH_STENCIL_EXT                              0x84F9
+#define GL_UNSIGNED_INT_24_8_EXT                          0x84FA
+#define GL_DEPTH24_STENCIL8_EXT                           0x88F0
+#define GL_TEXTURE_STENCIL_SIZE_EXT                       0x88F1
+#endif
+
+// GL_ARB_occlusion_query
+extern void (APIENTRY * qglGenQueriesARB)(GLsizei n, GLuint *ids);
+extern void (APIENTRY * qglDeleteQueriesARB)(GLsizei n, const GLuint *ids);
+extern GLboolean (APIENTRY * qglIsQueryARB)(GLuint id);
+extern void (APIENTRY * qglBeginQueryARB)(GLenum target, GLuint id);
+extern void (APIENTRY * qglEndQueryARB)(GLenum target);
+extern void (APIENTRY * qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params);
+extern void (APIENTRY * qglGetQueryObjectivARB)(GLuint id, GLenum pname, GLint *params);
+extern void (APIENTRY * qglGetQueryObjectuivARB)(GLuint id, GLenum pname, GLuint *params);
+
+#ifndef GL_ARB_occlusion_query
+#define GL_ARB_occlusion_query
+#define GL_SAMPLES_PASSED_ARB                             0x8914
+#define GL_QUERY_COUNTER_BITS_ARB                         0x8864
+#define GL_CURRENT_QUERY_ARB                              0x8865
+#define GL_QUERY_RESULT_ARB                               0x8866
+#define GL_QUERY_RESULT_AVAILABLE_ARB                     0x8867
+#endif
+
+// GL_EXT_framebuffer_blit
+extern void (APIENTRY * qglBlitFramebufferEXT)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
+                            GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
+                            GLbitfield mask, GLenum filter);
+
+#ifndef GL_EXT_framebuffer_blit
+#define GL_EXT_framebuffer_blit
+#define GL_READ_FRAMEBUFFER_EXT                0x8CA8
+#define GL_DRAW_FRAMEBUFFER_EXT                0x8CA9
+#define GL_DRAW_FRAMEBUFFER_BINDING_EXT        0x8CA6
+#define GL_READ_FRAMEBUFFER_BINDING_EXT        0x8CAA
+#endif
+
+// GL_EXT_framebuffer_multisample
+extern void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei samples,
+	GLenum internalformat, GLsizei width, GLsizei height);
+
+#ifndef GL_EXT_framebuffer_multisample
+#define GL_EXT_framebuffer_multisample
+#define GL_RENDERBUFFER_SAMPLES_EXT                0x8CAB
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT  0x8D56
+#define GL_MAX_SAMPLES_EXT                         0x8D57
+#endif
+
+#ifndef GL_EXT_texture_sRGB
+#define GL_EXT_texture_sRGB
+#define GL_SRGB_EXT                                       0x8C40
+#define GL_SRGB8_EXT                                      0x8C41
+#define GL_SRGB_ALPHA_EXT                                 0x8C42
+#define GL_SRGB8_ALPHA8_EXT                               0x8C43
+#define GL_SLUMINANCE_ALPHA_EXT                           0x8C44
+#define GL_SLUMINANCE8_ALPHA8_EXT                         0x8C45
+#define GL_SLUMINANCE_EXT                                 0x8C46
+#define GL_SLUMINANCE8_EXT                                0x8C47
+#define GL_COMPRESSED_SRGB_EXT                            0x8C48
+#define GL_COMPRESSED_SRGB_ALPHA_EXT                      0x8C49
+#define GL_COMPRESSED_SLUMINANCE_EXT                      0x8C4A
+#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT                0x8C4B
+#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT                  0x8C4C
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT            0x8C4D
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT            0x8C4E
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT            0x8C4F
+#endif
+
+#ifndef GL_EXT_framebuffer_sRGB
+#define GL_EXT_framebuffer_sRGB
+#define GL_FRAMEBUFFER_SRGB_EXT                         0x8DB9
+#endif
+
+#ifndef GL_EXT_texture_compression_latc
+#define GL_EXT_texture_compression_latc
+#define GL_COMPRESSED_LUMINANCE_LATC1_EXT                 0x8C70
+#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT          0x8C71
+#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT           0x8C72
+#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT    0x8C73
+#endif
+
+#ifndef GL_ARB_texture_compression_bptc
+#define GL_ARB_texture_compression_bptc
+#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB                 0x8E8C
+#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB           0x8E8D
+#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB           0x8E8E
+#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB         0x8E8F
+#endif
+
+// GL_ARB_draw_buffers
+extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
+#ifndef GL_ARB_draw_buffers
+#define GL_ARB_draw_buffers
+#define GL_MAX_DRAW_BUFFERS_ARB                    0x8824
+#define GL_DRAW_BUFFER0_ARB                        0x8825
+#define GL_DRAW_BUFFER1_ARB                        0x8826
+#define GL_DRAW_BUFFER2_ARB                        0x8827
+#define GL_DRAW_BUFFER3_ARB                        0x8828
+#define GL_DRAW_BUFFER4_ARB                        0x8829
+#define GL_DRAW_BUFFER5_ARB                        0x882A
+#define GL_DRAW_BUFFER6_ARB                        0x882B
+#define GL_DRAW_BUFFER7_ARB                        0x882C
+#define GL_DRAW_BUFFER8_ARB                        0x882D
+#define GL_DRAW_BUFFER9_ARB                        0x882E
+#define GL_DRAW_BUFFER10_ARB                       0x882F
+#define GL_DRAW_BUFFER11_ARB                       0x8830
+#define GL_DRAW_BUFFER12_ARB                       0x8831
+#define GL_DRAW_BUFFER13_ARB                       0x8832
+#define GL_DRAW_BUFFER14_ARB                       0x8833
+#define GL_DRAW_BUFFER15_ARB                       0x8834
+#endif
+
+#ifndef GL_ARB_depth_clamp
+#define GL_ARB_depth_clamp
+#define GL_DEPTH_CLAMP				      0x864F
+#endif
+
+#if defined(WIN32)
+// WGL_ARB_create_context
+#ifndef WGL_ARB_create_context
+#define WGL_CONTEXT_MAJOR_VERSION_ARB             0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB             0x2092
+#define WGL_CONTEXT_LAYER_PLANE_ARB               0x2093
+#define WGL_CONTEXT_FLAGS_ARB                     0x2094
+#define WGL_CONTEXT_PROFILE_MASK_ARB              0x9126
+#define WGL_CONTEXT_DEBUG_BIT_ARB                 0x0001
+#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB    0x0002
+#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB          0x00000001
+#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
+#define ERROR_INVALID_VERSION_ARB                 0x2095
+#define ERROR_INVALID_PROFILE_ARB                 0x2096
+#endif
+
+extern          HGLRC(APIENTRY * qwglCreateContextAttribsARB) (HDC hdC, HGLRC hShareContext, const int *attribList);
+#endif
+
+#if 0 //defined(__linux__)
+// GLX_ARB_create_context
+#ifndef GLX_ARB_create_context
+#define GLX_CONTEXT_DEBUG_BIT_ARB          0x00000001
+#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
+#define GLX_CONTEXT_MAJOR_VERSION_ARB      0x2091
+#define GLX_CONTEXT_MINOR_VERSION_ARB      0x2092
+#define GLX_CONTEXT_FLAGS_ARB              0x2094
+#endif
+
+extern GLXContext	(APIENTRY * qglXCreateContextAttribsARB) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list);
+#endif
+
+//===========================================================================
+
+#define qglAccum glAccum
+#define qglAlphaFunc glAlphaFunc
+#define qglAreTexturesResident glAreTexturesResident
+#define qglArrayElement glArrayElement
+#define qglBegin glBegin
+#define qglBindTexture glBindTexture
+#define qglBitmap glBitmap
+#define qglBlendFunc glBlendFunc
+#define qglCallList glCallList
+#define qglCallLists glCallLists
+#define qglClear glClear
+#define qglClearAccum glClearAccum
+#define qglClearColor glClearColor
+#define qglClearDepth glClearDepth
+#define qglClearIndex glClearIndex
+#define qglClearStencil glClearStencil
+#define qglClipPlane glClipPlane
+#define qglColor3b glColor3b
+#define qglColor3bv glColor3bv
+#define qglColor3d glColor3d
+#define qglColor3dv glColor3dv
+#define qglColor3f glColor3f
+#define qglColor3fv glColor3fv
+#define qglColor3i glColor3i
+#define qglColor3iv glColor3iv
+#define qglColor3s glColor3s
+#define qglColor3sv glColor3sv
+#define qglColor3ub glColor3ub
+#define qglColor3ubv glColor3ubv
+#define qglColor3ui glColor3ui
+#define qglColor3uiv glColor3uiv
+#define qglColor3us glColor3us
+#define qglColor3usv glColor3usv
+#define qglColor4b glColor4b
+#define qglColor4bv glColor4bv
+#define qglColor4d glColor4d
+#define qglColor4dv glColor4dv
+#define qglColor4f glColor4f
+#define qglColor4fv glColor4fv
+#define qglColor4i glColor4i
+#define qglColor4iv glColor4iv
+#define qglColor4s glColor4s
+#define qglColor4sv glColor4sv
+#define qglColor4ub glColor4ub
+#define qglColor4ubv glColor4ubv
+#define qglColor4ui glColor4ui
+#define qglColor4uiv glColor4uiv
+#define qglColor4us glColor4us
+#define qglColor4usv glColor4usv
+#define qglColorMask glColorMask
+#define qglColorMaterial glColorMaterial
+#define qglColorPointer glColorPointer
+#define qglCopyPixels glCopyPixels
+#define qglCopyTexImage1D glCopyTexImage1D
+#define qglCopyTexImage2D glCopyTexImage2D
+#define qglCopyTexSubImage1D glCopyTexSubImage1D
+#define qglCopyTexSubImage2D glCopyTexSubImage2D
+#define qglCullFace glCullFace
+#define qglDeleteLists glDeleteLists
+#define qglDeleteTextures glDeleteTextures
+#define qglDepthFunc glDepthFunc
+#define qglDepthMask glDepthMask
+#define qglDepthRange glDepthRange
+#define qglDisable glDisable
+#define qglDisableClientState glDisableClientState
+#define qglDrawArrays glDrawArrays
+#define qglDrawBuffer glDrawBuffer
+#define qglDrawElements glDrawElements
+#define qglDrawPixels glDrawPixels
+#define qglEdgeFlag glEdgeFlag
+#define qglEdgeFlagPointer glEdgeFlagPointer
+#define qglEdgeFlagv glEdgeFlagv
+#define qglEnable glEnable
+#define qglEnableClientState glEnableClientState
+#define qglEnd glEnd
+#define qglEndList glEndList
+#define qglEvalCoord1d glEvalCoord1d
+#define qglEvalCoord1dv glEvalCoord1dv
+#define qglEvalCoord1f glEvalCoord1f
+#define qglEvalCoord1fv glEvalCoord1fv
+#define qglEvalCoord2d glEvalCoord2d
+#define qglEvalCoord2dv glEvalCoord2dv
+#define qglEvalCoord2f glEvalCoord2f
+#define qglEvalCoord2fv glEvalCoord2fv
+#define qglEvalMesh1 glEvalMesh1
+#define qglEvalMesh2 glEvalMesh2
+#define qglEvalPoint1 glEvalPoint1
+#define qglEvalPoint2 glEvalPoint2
+#define qglFeedbackBuffer glFeedbackBuffer
+#define qglFinish glFinish
+#define qglFlush glFlush
+#define qglFogf glFogf
+#define qglFogfv glFogfv
+#define qglFogi glFogi
+#define qglFogiv glFogiv
+#define qglFrontFace glFrontFace
+#define qglFrustum glFrustum
+#define qglGenLists glGenLists
+#define qglGenTextures glGenTextures
+#define qglGetBooleanv glGetBooleanv
+#define qglGetClipPlane glGetClipPlane
+#define qglGetDoublev glGetDoublev
+#define qglGetError glGetError
+#define qglGetFloatv glGetFloatv
+#define qglGetIntegerv glGetIntegerv
+#define qglGetLightfv glGetLightfv
+#define qglGetLightiv glGetLightiv
+#define qglGetMapdv glGetMapdv
+#define qglGetMapfv glGetMapfv
+#define qglGetMapiv glGetMapiv
+#define qglGetMaterialfv glGetMaterialfv
+#define qglGetMaterialiv glGetMaterialiv
+#define qglGetPixelMapfv glGetPixelMapfv
+#define qglGetPixelMapuiv glGetPixelMapuiv
+#define qglGetPixelMapusv glGetPixelMapusv
+#define qglGetPointerv glGetPointerv
+#define qglGetPolygonStipple glGetPolygonStipple
+#define qglGetString glGetString
+#define qglGetTexGendv glGetTexGendv
+#define qglGetTexGenfv glGetTexGenfv
+#define qglGetTexGeniv glGetTexGeniv
+#define qglGetTexImage glGetTexImage
+#define qglGetTexLevelParameterfv glGetTexLevelParameterfv
+#define qglGetTexLevelParameteriv glGetTexLevelParameteriv
+#define qglGetTexParameterfv glGetTexParameterfv
+#define qglGetTexParameteriv glGetTexParameteriv
+#define qglHint glHint
+#define qglIndexMask glIndexMask
+#define qglIndexPointer glIndexPointer
+#define qglIndexd glIndexd
+#define qglIndexdv glIndexdv
+#define qglIndexf glIndexf
+#define qglIndexfv glIndexfv
+#define qglIndexi glIndexi
+#define qglIndexiv glIndexiv
+#define qglIndexs glIndexs
+#define qglIndexsv glIndexsv
+#define qglIndexub glIndexub
+#define qglIndexubv glIndexubv
+#define qglInitNames glInitNames
+#define qglInterleavedArrays glInterleavedArrays
+#define qglIsEnabled glIsEnabled
+#define qglIsList glIsList
+#define qglIsTexture glIsTexture
+#define qglLightModelf glLightModelf
+#define qglLightModelfv glLightModelfv
+#define qglLightModeli glLightModeli
+#define qglLightModeliv glLightModeliv
+#define qglLightf glLightf
+#define qglLightfv glLightfv
+#define qglLighti glLighti
+#define qglLightiv glLightiv
+#define qglLineStipple glLineStipple
+#define qglLineWidth glLineWidth
+#define qglListBase glListBase
+#define qglLoadIdentity glLoadIdentity
+#define qglLoadMatrixd glLoadMatrixd
+#define qglLoadMatrixf glLoadMatrixf
+#define qglLoadName glLoadName
+#define qglLogicOp glLogicOp
+#define qglMap1d glMap1d
+#define qglMap1f glMap1f
+#define qglMap2d glMap2d
+#define qglMap2f glMap2f
+#define qglMapGrid1d glMapGrid1d
+#define qglMapGrid1f glMapGrid1f
+#define qglMapGrid2d glMapGrid2d
+#define qglMapGrid2f glMapGrid2f
+#define qglMaterialf glMaterialf
+#define qglMaterialfv glMaterialfv
+#define qglMateriali glMateriali
+#define qglMaterialiv glMaterialiv
+#define qglMatrixMode glMatrixMode
+#define qglMultMatrixd glMultMatrixd
+#define qglMultMatrixf glMultMatrixf
+#define qglNewList glNewList
+#define qglNormal3b glNormal3b
+#define qglNormal3bv glNormal3bv
+#define qglNormal3d glNormal3d
+#define qglNormal3dv glNormal3dv
+#define qglNormal3f glNormal3f
+#define qglNormal3fv glNormal3fv
+#define qglNormal3i glNormal3i
+#define qglNormal3iv glNormal3iv
+#define qglNormal3s glNormal3s
+#define qglNormal3sv glNormal3sv
+#define qglNormalPointer glNormalPointer
+#define qglOrtho glOrtho
+#define qglPassThrough glPassThrough
+#define qglPixelMapfv glPixelMapfv
+#define qglPixelMapuiv glPixelMapuiv
+#define qglPixelMapusv glPixelMapusv
+#define qglPixelStoref glPixelStoref
+#define qglPixelStorei glPixelStorei
+#define qglPixelTransferf glPixelTransferf
+#define qglPixelTransferi glPixelTransferi
+#define qglPixelZoom glPixelZoom
+#define qglPointSize glPointSize
+#define qglPolygonMode glPolygonMode
+#define qglPolygonOffset glPolygonOffset
+#define qglPolygonStipple glPolygonStipple
+#define qglPopAttrib glPopAttrib
+#define qglPopClientAttrib glPopClientAttrib
+#define qglPopMatrix glPopMatrix
+#define qglPopName glPopName
+#define qglPrioritizeTextures glPrioritizeTextures
+#define qglPushAttrib glPushAttrib
+#define qglPushClientAttrib glPushClientAttrib
+#define qglPushMatrix glPushMatrix
+#define qglPushName glPushName
+#define qglRasterPos2d glRasterPos2d
+#define qglRasterPos2dv glRasterPos2dv
+#define qglRasterPos2f glRasterPos2f
+#define qglRasterPos2fv glRasterPos2fv
+#define qglRasterPos2i glRasterPos2i
+#define qglRasterPos2iv glRasterPos2iv
+#define qglRasterPos2s glRasterPos2s
+#define qglRasterPos2sv glRasterPos2sv
+#define qglRasterPos3d glRasterPos3d
+#define qglRasterPos3dv glRasterPos3dv
+#define qglRasterPos3f glRasterPos3f
+#define qglRasterPos3fv glRasterPos3fv
+#define qglRasterPos3i glRasterPos3i
+#define qglRasterPos3iv glRasterPos3iv
+#define qglRasterPos3s glRasterPos3s
+#define qglRasterPos3sv glRasterPos3sv
+#define qglRasterPos4d glRasterPos4d
+#define qglRasterPos4dv glRasterPos4dv
+#define qglRasterPos4f glRasterPos4f
+#define qglRasterPos4fv glRasterPos4fv
+#define qglRasterPos4i glRasterPos4i
+#define qglRasterPos4iv glRasterPos4iv
+#define qglRasterPos4s glRasterPos4s
+#define qglRasterPos4sv glRasterPos4sv
+#define qglReadBuffer glReadBuffer
+#define qglReadPixels glReadPixels
+#define qglRectd glRectd
+#define qglRectdv glRectdv
+#define qglRectf glRectf
+#define qglRectfv glRectfv
+#define qglRecti glRecti
+#define qglRectiv glRectiv
+#define qglRects glRects
+#define qglRectsv glRectsv
+#define qglRenderMode glRenderMode
+#define qglRotated glRotated
+#define qglRotatef glRotatef
+#define qglScaled glScaled
+#define qglScalef glScalef
+#define qglScissor glScissor
+#define qglSelectBuffer glSelectBuffer
+#define qglShadeModel glShadeModel
+#define qglStencilFunc glStencilFunc
+#define qglStencilMask glStencilMask
+#define qglStencilOp glStencilOp
+#define qglTexCoord1d glTexCoord1d
+#define qglTexCoord1dv glTexCoord1dv
+#define qglTexCoord1f glTexCoord1f
+#define qglTexCoord1fv glTexCoord1fv
+#define qglTexCoord1i glTexCoord1i
+#define qglTexCoord1iv glTexCoord1iv
+#define qglTexCoord1s glTexCoord1s
+#define qglTexCoord1sv glTexCoord1sv
+#define qglTexCoord2d glTexCoord2d
+#define qglTexCoord2dv glTexCoord2dv
+#define qglTexCoord2f glTexCoord2f
+#define qglTexCoord2fv glTexCoord2fv
+#define qglTexCoord2i glTexCoord2i
+#define qglTexCoord2iv glTexCoord2iv
+#define qglTexCoord2s glTexCoord2s
+#define qglTexCoord2sv glTexCoord2sv
+#define qglTexCoord3d glTexCoord3d
+#define qglTexCoord3dv glTexCoord3dv
+#define qglTexCoord3f glTexCoord3f
+#define qglTexCoord3fv glTexCoord3fv
+#define qglTexCoord3i glTexCoord3i
+#define qglTexCoord3iv glTexCoord3iv
+#define qglTexCoord3s glTexCoord3s
+#define qglTexCoord3sv glTexCoord3sv
+#define qglTexCoord4d glTexCoord4d
+#define qglTexCoord4dv glTexCoord4dv
+#define qglTexCoord4f glTexCoord4f
+#define qglTexCoord4fv glTexCoord4fv
+#define qglTexCoord4i glTexCoord4i
+#define qglTexCoord4iv glTexCoord4iv
+#define qglTexCoord4s glTexCoord4s
+#define qglTexCoord4sv glTexCoord4sv
+#define qglTexCoordPointer glTexCoordPointer
+#define qglTexEnvf glTexEnvf
+#define qglTexEnvfv glTexEnvfv
+#define qglTexEnvi glTexEnvi
+#define qglTexEnviv glTexEnviv
+#define qglTexGend glTexGend
+#define qglTexGendv glTexGendv
+#define qglTexGenf glTexGenf
+#define qglTexGenfv glTexGenfv
+#define qglTexGeni glTexGeni
+#define qglTexGeniv glTexGeniv
+#define qglTexImage1D glTexImage1D
+#define qglTexImage2D glTexImage2D
+#define qglTexParameterf glTexParameterf
+#define qglTexParameterfv glTexParameterfv
+#define qglTexParameteri glTexParameteri
+#define qglTexParameteriv glTexParameteriv
+#define qglTexSubImage1D glTexSubImage1D
+#define qglTexSubImage2D glTexSubImage2D
+#define qglTranslated glTranslated
+#define qglTranslatef glTranslatef
+#define qglVertex2d glVertex2d
+#define qglVertex2dv glVertex2dv
+#define qglVertex2f glVertex2f
+#define qglVertex2fv glVertex2fv
+#define qglVertex2i glVertex2i
+#define qglVertex2iv glVertex2iv
+#define qglVertex2s glVertex2s
+#define qglVertex2sv glVertex2sv
+#define qglVertex3d glVertex3d
+#define qglVertex3dv glVertex3dv
+#define qglVertex3f glVertex3f
+#define qglVertex3fv glVertex3fv
+#define qglVertex3i glVertex3i
+#define qglVertex3iv glVertex3iv
+#define qglVertex3s glVertex3s
+#define qglVertex3sv glVertex3sv
+#define qglVertex4d glVertex4d
+#define qglVertex4dv glVertex4dv
+#define qglVertex4f glVertex4f
+#define qglVertex4fv glVertex4fv
+#define qglVertex4i glVertex4i
+#define qglVertex4iv glVertex4iv
+#define qglVertex4s glVertex4s
+#define qglVertex4sv glVertex4sv
+#define qglVertexPointer glVertexPointer
+#define qglViewport glViewport
+
+#endif

Added: trunk/code/rend2/tr_animation.c
===================================================================
--- trunk/code/rend2/tr_animation.c	                        (rev 0)
+++ trunk/code/rend2/tr_animation.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,658 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+
+All bones should be an identity orientation to display the mesh exactly
+as it is specified.
+
+For all other frames, the bones represent the transformation from the 
+orientation of the bone in the base frame to the orientation in this
+frame.
+
+*/
+
+/*
+==============
+R_AddAnimSurfaces
+==============
+*/
+void R_AddAnimSurfaces( trRefEntity_t *ent ) {
+	md4Header_t		*header;
+	md4Surface_t	*surface;
+	md4LOD_t		*lod;
+	shader_t		*shader;
+	int				i;
+
+	header = (md4Header_t *) tr.currentModel->modelData;
+	lod = (md4LOD_t *)( (byte *)header + header->ofsLODs );
+
+	surface = (md4Surface_t *)( (byte *)lod + lod->ofsSurfaces );
+	for ( i = 0 ; i < lod->numSurfaces ; i++ ) {
+		shader = R_GetShaderByHandle( surface->shaderIndex );
+		R_AddDrawSurf( (void *)surface, shader, 0 /*fogNum*/, qfalse, qfalse );
+		surface = (md4Surface_t *)( (byte *)surface + surface->ofsEnd );
+	}
+}
+
+/*
+==============
+RB_SurfaceAnim
+==============
+*/
+void RB_SurfaceAnim( md4Surface_t *surface ) {
+	int				i, j, k;
+	float			frontlerp, backlerp;
+	int				*triangles;
+	int				indexes;
+	int				baseIndex, baseVertex;
+	int				numVerts;
+	md4Vertex_t		*v;
+	md4Bone_t		bones[MD4_MAX_BONES];
+	md4Bone_t		*bonePtr, *bone;
+	md4Header_t		*header;
+	md4Frame_t		*frame;
+	md4Frame_t		*oldFrame;
+	int				frameSize;
+
+
+	if (  backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) {
+		backlerp = 0;
+		frontlerp = 1;
+	} else  {
+		backlerp = backEnd.currentEntity->e.backlerp;
+		frontlerp = 1.0f - backlerp;
+	}
+	header = (md4Header_t *)((byte *)surface + surface->ofsHeader);
+
+	frameSize = (size_t)( &((md4Frame_t *)0)->bones[ header->numBones ] );
+
+	frame = (md4Frame_t *)((byte *)header + header->ofsFrames + 
+			backEnd.currentEntity->e.frame * frameSize );
+	oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + 
+			backEnd.currentEntity->e.oldframe * frameSize );
+
+	RB_CheckOverflow( surface->numVerts, surface->numTriangles * 3 );
+
+	triangles = (int *) ((byte *)surface + surface->ofsTriangles);
+	indexes = surface->numTriangles * 3;
+	baseIndex = tess.numIndexes;
+	baseVertex = tess.numVertexes;
+	for (j = 0 ; j < indexes ; j++) {
+		tess.indexes[baseIndex + j] = baseIndex + triangles[j];
+	}
+	tess.numIndexes += indexes;
+
+	//
+	// lerp all the needed bones
+	//
+	if ( !backlerp ) {
+		// no lerping needed
+		bonePtr = frame->bones;
+	} else {
+		bonePtr = bones;
+		for ( i = 0 ; i < header->numBones*12 ; i++ ) {
+			((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i]
+					+ backlerp * ((float *)oldFrame->bones)[i];
+		}
+	}
+
+	//
+	// deform the vertexes by the lerped bones
+	//
+	numVerts = surface->numVerts;
+	// FIXME
+	// This makes TFC's skeletons work.  Shouldn't be necessary anymore, but left
+	// in for reference.
+	//v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts + 12);
+	v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts);
+	for ( j = 0; j < numVerts; j++ ) {
+		vec3_t	tempVert, tempNormal;
+		md4Weight_t	*w;
+
+		VectorClear( tempVert );
+		VectorClear( tempNormal );
+		w = v->weights;
+		for ( k = 0 ; k < v->numWeights ; k++, w++ ) {
+			bone = bonePtr + w->boneIndex;
+
+			tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] );
+			tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] );
+			tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] );
+
+			tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal );
+			tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal );
+			tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal );
+		}
+
+		tess.xyz[baseVertex + j][0] = tempVert[0];
+		tess.xyz[baseVertex + j][1] = tempVert[1];
+		tess.xyz[baseVertex + j][2] = tempVert[2];
+
+		tess.normal[baseVertex + j][0] = tempNormal[0];
+		tess.normal[baseVertex + j][1] = tempNormal[1];
+		tess.normal[baseVertex + j][2] = tempNormal[2];
+
+		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
+		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
+
+		// FIXME
+		// This makes TFC's skeletons work.  Shouldn't be necessary anymore, but left
+		// in for reference.
+		//v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 );
+		v = (md4Vertex_t *)&v->weights[v->numWeights];
+	}
+
+	tess.numVertexes += surface->numVerts;
+}
+
+
+#ifdef RAVENMD4
+
+// copied and adapted from tr_mesh.c
+
+/*
+=============
+R_MDRCullModel
+=============
+*/
+
+static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) {
+	vec3_t		bounds[2];
+	mdrFrame_t	*oldFrame, *newFrame;
+	int			i, frameSize;
+
+	frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+	
+	// compute frame pointers
+	newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame);
+	oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe);
+
+	// cull bounding sphere ONLY if this is not an upscaled entity
+	if ( !ent->e.nonNormalizedAxes )
+	{
+		if ( ent->e.frame == ent->e.oldframe )
+		{
+			switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) )
+			{
+				// Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend
+				// we do. After all, the purpose of md4s are not that different, are they?
+				
+				case CULL_OUT:
+					tr.pc.c_sphere_cull_md3_out++;
+					return CULL_OUT;
+
+				case CULL_IN:
+					tr.pc.c_sphere_cull_md3_in++;
+					return CULL_IN;
+
+				case CULL_CLIP:
+					tr.pc.c_sphere_cull_md3_clip++;
+					break;
+			}
+		}
+		else
+		{
+			int sphereCull, sphereCullB;
+
+			sphereCull  = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius );
+			if ( newFrame == oldFrame ) {
+				sphereCullB = sphereCull;
+			} else {
+				sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius );
+			}
+
+			if ( sphereCull == sphereCullB )
+			{
+				if ( sphereCull == CULL_OUT )
+				{
+					tr.pc.c_sphere_cull_md3_out++;
+					return CULL_OUT;
+				}
+				else if ( sphereCull == CULL_IN )
+				{
+					tr.pc.c_sphere_cull_md3_in++;
+					return CULL_IN;
+				}
+				else
+				{
+					tr.pc.c_sphere_cull_md3_clip++;
+				}
+			}
+		}
+	}
+	
+	// calculate a bounding box in the current coordinate system
+	for (i = 0 ; i < 3 ; i++) {
+		bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i];
+		bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i];
+	}
+
+	switch ( R_CullLocalBox( bounds ) )
+	{
+		case CULL_IN:
+			tr.pc.c_box_cull_md3_in++;
+			return CULL_IN;
+		case CULL_CLIP:
+			tr.pc.c_box_cull_md3_clip++;
+			return CULL_CLIP;
+		case CULL_OUT:
+		default:
+			tr.pc.c_box_cull_md3_out++;
+			return CULL_OUT;
+	}
+}
+
+/*
+=================
+R_MDRComputeFogNum
+
+=================
+*/
+
+int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) {
+	int				i, j;
+	fog_t			*fog;
+	mdrFrame_t		*mdrFrame;
+	vec3_t			localOrigin;
+	int frameSize;
+
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		return 0;
+	}
+	
+	frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+
+	// FIXME: non-normalized axis issues
+	mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame);
+	VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin );
+	for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+		fog = &tr.world->fogs[i];
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) {
+				break;
+			}
+			if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) {
+				break;
+			}
+		}
+		if ( j == 3 ) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+
+/*
+==============
+R_MDRAddAnimSurfaces
+==============
+*/
+
+// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c
+
+void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) {
+	mdrHeader_t		*header;
+	mdrSurface_t	*surface;
+	mdrLOD_t		*lod;
+	shader_t		*shader;
+	skin_t		*skin;
+	int				i, j;
+	int				lodnum = 0;
+	int				fogNum = 0;
+	int				cull;
+	qboolean	personalModel;
+
+	header = (mdrHeader_t *) tr.currentModel->modelData;
+	
+	personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal;
+	
+	if ( ent->e.renderfx & RF_WRAP_FRAMES )
+	{
+		ent->e.frame %= header->numFrames;
+		ent->e.oldframe %= header->numFrames;
+	}	
+	
+	//
+	// Validate the frames so there is no chance of a crash.
+	// This will write directly into the entity structure, so
+	// when the surfaces are rendered, they don't need to be
+	// range checked again.
+	//
+	if ((ent->e.frame >= header->numFrames) 
+		|| (ent->e.frame < 0)
+		|| (ent->e.oldframe >= header->numFrames)
+		|| (ent->e.oldframe < 0) )
+	{
+		ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n",
+			   ent->e.oldframe, ent->e.frame, tr.currentModel->name );
+		ent->e.frame = 0;
+		ent->e.oldframe = 0;
+	}
+
+	//
+	// cull the entire model if merged bounding box of both frames
+	// is outside the view frustum.
+	//
+	cull = R_MDRCullModel (header, ent);
+	if ( cull == CULL_OUT ) {
+		return;
+	}	
+
+	// figure out the current LOD of the model we're rendering, and set the lod pointer respectively.
+	lodnum = R_ComputeLOD(ent);
+	// check whether this model has as that many LODs at all. If not, try the closest thing we got.
+	if(header->numLODs <= 0)
+		return;
+	if(header->numLODs <= lodnum)
+		lodnum = header->numLODs - 1;
+
+	lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs);
+	for(i = 0; i < lodnum; i++)
+	{
+		lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd);
+	}
+	
+	// set up lighting
+	if ( !personalModel || r_shadows->integer > 1 )
+	{
+		R_SetupEntityLighting( &tr.refdef, ent );
+	}
+
+	// fogNum?
+	fogNum = R_MDRComputeFogNum( header, ent );
+
+	surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces );
+
+	for ( i = 0 ; i < lod->numSurfaces ; i++ )
+	{
+		
+		if(ent->e.customShader)
+			shader = R_GetShaderByHandle(ent->e.customShader);
+		else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins)
+		{
+			skin = R_GetSkinByHandle(ent->e.customSkin);
+			shader = tr.defaultShader;
+			
+			for(j = 0; j < skin->numSurfaces; j++)
+			{
+				if (!strcmp(skin->surfaces[j]->name, surface->name))
+				{
+					shader = skin->surfaces[j]->shader;
+					break;
+				}
+			}
+		}
+		else if(surface->shaderIndex > 0)
+			shader = R_GetShaderByHandle( surface->shaderIndex );
+		else
+			shader = tr.defaultShader;
+
+		// we will add shadows even if the main object isn't visible in the view
+
+		// stencil shadows can't do personal models unless I polyhedron clip
+		if ( !personalModel
+		        && r_shadows->integer == 2
+			&& fogNum == 0
+			&& !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
+			&& shader->sort == SS_OPAQUE )
+		{
+			R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse, qfalse );
+		}
+
+		// projection shadows work fine with personal models
+		if ( r_shadows->integer == 3
+			&& fogNum == 0
+			&& (ent->e.renderfx & RF_SHADOW_PLANE )
+			&& shader->sort == SS_OPAQUE )
+		{
+			R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse, qfalse );
+		}
+
+		if (!personalModel)
+			R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse, qfalse );
+
+		surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd );
+	}
+}
+
+/*
+==============
+RB_MDRSurfaceAnim
+==============
+*/
+void RB_MDRSurfaceAnim( md4Surface_t *surface )
+{
+	int				i, j, k;
+	float			frontlerp, backlerp;
+	int				*triangles;
+	int				indexes;
+	int				baseIndex, baseVertex;
+	int				numVerts;
+	mdrVertex_t		*v;
+	mdrHeader_t		*header;
+	mdrFrame_t		*frame;
+	mdrFrame_t		*oldFrame;
+	mdrBone_t		bones[MD4_MAX_BONES], *bonePtr, *bone;
+
+	int			frameSize;
+
+	// don't lerp if lerping off, or this is the only frame, or the last frame...
+	//
+	if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame) 
+	{
+		backlerp	= 0;	// if backlerp is 0, lerping is off and frontlerp is never used
+		frontlerp	= 1;
+	} 
+	else  
+	{
+		backlerp	= backEnd.currentEntity->e.backlerp;
+		frontlerp	= 1.0f - backlerp;
+	}
+
+	header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader);
+
+	frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+
+	frame = (mdrFrame_t *)((byte *)header + header->ofsFrames +
+		backEnd.currentEntity->e.frame * frameSize );
+	oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames +
+		backEnd.currentEntity->e.oldframe * frameSize );
+
+	RB_CheckOverflow( surface->numVerts, surface->numTriangles );
+
+	triangles	= (int *) ((byte *)surface + surface->ofsTriangles);
+	indexes		= surface->numTriangles * 3;
+	baseIndex	= tess.numIndexes;
+	baseVertex	= tess.numVertexes;
+	
+	// Set up all triangles.
+	for (j = 0 ; j < indexes ; j++) 
+	{
+		tess.indexes[baseIndex + j] = baseVertex + triangles[j];
+	}
+	tess.numIndexes += indexes;
+
+	//
+	// lerp all the needed bones
+	//
+	if ( !backlerp ) 
+	{
+		// no lerping needed
+		bonePtr = frame->bones;
+	} 
+	else 
+	{
+		bonePtr = bones;
+		
+		for ( i = 0 ; i < header->numBones*12 ; i++ ) 
+		{
+			((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i];
+		}
+	}
+
+	//
+	// deform the vertexes by the lerped bones
+	//
+	numVerts = surface->numVerts;
+	v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts);
+	for ( j = 0; j < numVerts; j++ ) 
+	{
+		vec3_t	tempVert, tempNormal;
+		mdrWeight_t	*w;
+
+		VectorClear( tempVert );
+		VectorClear( tempNormal );
+		w = v->weights;
+		for ( k = 0 ; k < v->numWeights ; k++, w++ ) 
+		{
+			bone = bonePtr + w->boneIndex;
+			
+			tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] );
+			tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] );
+			tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] );
+			
+			tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal );
+			tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal );
+			tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal );
+		}
+
+		tess.xyz[baseVertex + j][0] = tempVert[0];
+		tess.xyz[baseVertex + j][1] = tempVert[1];
+		tess.xyz[baseVertex + j][2] = tempVert[2];
+
+		tess.normal[baseVertex + j][0] = tempNormal[0];
+		tess.normal[baseVertex + j][1] = tempNormal[1];
+		tess.normal[baseVertex + j][2] = tempNormal[2];
+
+		tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
+		tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
+
+		v = (mdrVertex_t *)&v->weights[v->numWeights];
+	}
+
+	tess.numVertexes += surface->numVerts;
+}
+
+
+#define MC_MASK_X ((1<<(MC_BITS_X))-1)
+#define MC_MASK_Y ((1<<(MC_BITS_Y))-1)
+#define MC_MASK_Z ((1<<(MC_BITS_Z))-1)
+#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1)
+
+#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2))
+
+#define MC_POS_X (0)
+#define MC_SHIFT_X (0)
+
+#define MC_POS_Y ((((MC_BITS_X))/8))
+#define MC_SHIFT_Y ((((MC_BITS_X)%8)))
+
+#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8))
+#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8)))
+
+#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8))
+#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8)))
+
+#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8))
+#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8)))
+
+#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8))
+#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8)))
+
+#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8))
+#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8)))
+
+#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8))
+#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8)))
+
+#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8))
+#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8)))
+
+#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8))
+#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8)))
+
+#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8))
+#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8)))
+
+#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8))
+#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8)))
+
+void MC_UnCompress(float mat[3][4],const unsigned char * comp)
+{
+	int val;
+
+	val=(int)((unsigned short *)(comp))[0];
+	val-=1<<(MC_BITS_X-1);
+	mat[0][3]=((float)(val))*MC_SCALE_X;
+
+	val=(int)((unsigned short *)(comp))[1];
+	val-=1<<(MC_BITS_Y-1);
+	mat[1][3]=((float)(val))*MC_SCALE_Y;
+
+	val=(int)((unsigned short *)(comp))[2];
+	val-=1<<(MC_BITS_Z-1);
+	mat[2][3]=((float)(val))*MC_SCALE_Z;
+
+	val=(int)((unsigned short *)(comp))[3];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[0][0]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[4];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[0][1]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[5];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[0][2]=((float)(val))*MC_SCALE_VECT;
+
+
+	val=(int)((unsigned short *)(comp))[6];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[1][0]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[7];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[1][1]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[8];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[1][2]=((float)(val))*MC_SCALE_VECT;
+
+
+	val=(int)((unsigned short *)(comp))[9];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[2][0]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[10];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[2][1]=((float)(val))*MC_SCALE_VECT;
+
+	val=(int)((unsigned short *)(comp))[11];
+	val-=1<<(MC_BITS_VECT-1);
+	mat[2][2]=((float)(val))*MC_SCALE_VECT;
+}
+#endif

Added: trunk/code/rend2/tr_backend.c
===================================================================
--- trunk/code/rend2/tr_backend.c	                        (rev 0)
+++ trunk/code/rend2/tr_backend.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1912 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+backEndData_t	*backEndData[SMP_FRAMES];
+backEndState_t	backEnd;
+
+
+static float	s_flipMatrix[16] = {
+	// convert from our coordinate system (looking down X)
+	// to OpenGL's coordinate system (looking down -Z)
+	0, 0, -1, 0,
+	-1, 0, 0, 0,
+	0, 1, 0, 0,
+	0, 0, 0, 1
+};
+
+
+/*
+** GL_Bind2
+*/
+void GL_Bind2( image_t *image, GLenum type ) {
+	int texnum;
+
+	if ( !image ) {
+		ri.Printf( PRINT_WARNING, "GL_Bind2: NULL image\n" );
+		texnum = tr.defaultImage->texnum;
+	} else {
+		texnum = image->texnum;
+	}
+
+	if ( r_nobind->integer && tr.dlightImage ) {		// performance evaluation option
+		texnum = tr.dlightImage->texnum;
+	}
+
+	if ( glState.currenttextures[glState.currenttmu] != texnum ) {
+		image->frameUsed = tr.frameCount;
+		glState.currenttextures[glState.currenttmu] = texnum;
+		qglBindTexture (type, texnum);
+	}
+}
+
+/*
+** GL_Bind2
+*/
+void GL_Bind( image_t *image )
+{
+	GL_Bind2( image, GL_TEXTURE_2D );
+}
+
+/*
+** GL_BindCubemap
+*/
+void GL_BindCubemap( image_t *image )
+{
+	GL_Bind2( image, GL_TEXTURE_CUBE_MAP );
+}
+
+/*
+** GL_SelectTexture
+*/
+void GL_SelectTexture( int unit )
+{
+	if ( glState.currenttmu == unit )
+	{
+		return;
+	}
+
+	if (!(unit >= 0 && unit <= 31))
+		ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
+
+	qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
+
+	glState.currenttmu = unit;
+}
+
+
+/*
+** GL_BindMultitexture
+*/
+void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) {
+	int		texnum0, texnum1;
+
+	texnum0 = image0->texnum;
+	texnum1 = image1->texnum;
+
+	if ( r_nobind->integer && tr.dlightImage ) {		// performance evaluation option
+		texnum0 = texnum1 = tr.dlightImage->texnum;
+	}
+
+	if ( glState.currenttextures[1] != texnum1 ) {
+		GL_SelectTexture( 1 );
+		image1->frameUsed = tr.frameCount;
+		glState.currenttextures[1] = texnum1;
+		qglBindTexture( GL_TEXTURE_2D, texnum1 );
+	}
+	if ( glState.currenttextures[0] != texnum0 ) {
+		GL_SelectTexture( 0 );
+		image0->frameUsed = tr.frameCount;
+		glState.currenttextures[0] = texnum0;
+		qglBindTexture( GL_TEXTURE_2D, texnum0 );
+	}
+}
+
+/*
+** GL_BindToTMU
+*/
+void GL_BindToTMU( image_t *image, int tmu )
+{
+	int		texnum;
+	int     oldtmu = glState.currenttmu;
+
+	if (!image)
+		texnum = 0;
+	else
+		texnum = image->texnum;
+
+	if ( glState.currenttextures[tmu] != texnum ) {
+		GL_SelectTexture( tmu );
+		if (image)
+			image->frameUsed = tr.frameCount;
+		glState.currenttextures[tmu] = texnum;
+		qglBindTexture( GL_TEXTURE_2D, texnum );
+		GL_SelectTexture( oldtmu );
+	}
+}
+
+
+/*
+** GL_Cull
+*/
+void GL_Cull( int cullType ) {
+#ifdef REACTION
+	// Makro - flip culling if needed
+	qboolean flip = (backEnd.currentEntity != NULL && backEnd.currentEntity->mirrored != qfalse && cullType != CT_TWO_SIDED);
+	cullType ^= flip;	// this assumes CT_BACK_SIDED and CT_FRONT_SIDED are 0 or 1
+#endif
+
+	if ( glState.faceCulling == cullType ) {
+		return;
+	}
+
+	glState.faceCulling = cullType;
+
+	if ( cullType == CT_TWO_SIDED ) 
+	{
+		qglDisable( GL_CULL_FACE );
+	} 
+	else 
+	{
+		qboolean cullFront;
+		qglEnable( GL_CULL_FACE );
+
+		cullFront = (cullType == CT_FRONT_SIDED);
+		if ( backEnd.viewParms.isMirror )
+		{
+			cullFront = !cullFront;
+		}
+
+		qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+	}
+}
+
+/*
+** GL_TexEnv
+*/
+void GL_TexEnv( int env )
+{
+	if ( env == glState.texEnv[glState.currenttmu] )
+	{
+		return;
+	}
+
+	glState.texEnv[glState.currenttmu] = env;
+
+
+	switch ( env )
+	{
+	case GL_MODULATE:
+		qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
+		break;
+	case GL_REPLACE:
+		qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
+		break;
+	case GL_DECAL:
+		qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
+		break;
+	case GL_ADD:
+		qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
+		break;
+	default:
+		ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed", env );
+		break;
+	}
+}
+
+/*
+** GL_State
+**
+** This routine is responsible for setting the most commonly changed state
+** in Q3.
+*/
+void GL_State( unsigned long stateBits )
+{
+	unsigned long diff = stateBits ^ glState.glStateBits;
+
+	if ( !diff )
+	{
+		return;
+	}
+
+	//
+	// check depthFunc bits
+	//
+	if ( diff & GLS_DEPTHFUNC_BITS )
+	{
+		if ( stateBits & GLS_DEPTHFUNC_EQUAL )
+		{
+			qglDepthFunc( GL_EQUAL );
+		}
+		else if ( stateBits & GLS_DEPTHFUNC_GREATER)
+		{
+			qglDepthFunc( GL_GREATER );
+		}
+		else
+		{
+			qglDepthFunc( GL_LEQUAL );
+		}
+	}
+
+	//
+	// check blend bits
+	//
+	if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
+	{
+		GLenum srcFactor, dstFactor;
+
+		if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
+		{
+			switch ( stateBits & GLS_SRCBLEND_BITS )
+			{
+			case GLS_SRCBLEND_ZERO:
+				srcFactor = GL_ZERO;
+				break;
+			case GLS_SRCBLEND_ONE:
+				srcFactor = GL_ONE;
+				break;
+			case GLS_SRCBLEND_DST_COLOR:
+				srcFactor = GL_DST_COLOR;
+				break;
+			case GLS_SRCBLEND_ONE_MINUS_DST_COLOR:
+				srcFactor = GL_ONE_MINUS_DST_COLOR;
+				break;
+			case GLS_SRCBLEND_SRC_ALPHA:
+				srcFactor = GL_SRC_ALPHA;
+				break;
+			case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA:
+				srcFactor = GL_ONE_MINUS_SRC_ALPHA;
+				break;
+			case GLS_SRCBLEND_DST_ALPHA:
+				srcFactor = GL_DST_ALPHA;
+				break;
+			case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA:
+				srcFactor = GL_ONE_MINUS_DST_ALPHA;
+				break;
+			case GLS_SRCBLEND_ALPHA_SATURATE:
+				srcFactor = GL_SRC_ALPHA_SATURATE;
+				break;
+			default:
+				srcFactor = GL_ONE;		// to get warning to shut up
+				ri.Error( ERR_DROP, "GL_State: invalid src blend state bits" );
+				break;
+			}
+
+			switch ( stateBits & GLS_DSTBLEND_BITS )
+			{
+			case GLS_DSTBLEND_ZERO:
+				dstFactor = GL_ZERO;
+				break;
+			case GLS_DSTBLEND_ONE:
+				dstFactor = GL_ONE;
+				break;
+			case GLS_DSTBLEND_SRC_COLOR:
+				dstFactor = GL_SRC_COLOR;
+				break;
+			case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
+				dstFactor = GL_ONE_MINUS_SRC_COLOR;
+				break;
+			case GLS_DSTBLEND_SRC_ALPHA:
+				dstFactor = GL_SRC_ALPHA;
+				break;
+			case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
+				dstFactor = GL_ONE_MINUS_SRC_ALPHA;
+				break;
+			case GLS_DSTBLEND_DST_ALPHA:
+				dstFactor = GL_DST_ALPHA;
+				break;
+			case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
+				dstFactor = GL_ONE_MINUS_DST_ALPHA;
+				break;
+			default:
+				dstFactor = GL_ONE;		// to get warning to shut up
+				ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits" );
+				break;
+			}
+
+			qglEnable( GL_BLEND );
+			qglBlendFunc( srcFactor, dstFactor );
+		}
+		else
+		{
+			qglDisable( GL_BLEND );
+		}
+	}
+
+	//
+	// check depthmask
+	//
+	if ( diff & GLS_DEPTHMASK_TRUE )
+	{
+		if ( stateBits & GLS_DEPTHMASK_TRUE )
+		{
+			qglDepthMask( GL_TRUE );
+		}
+		else
+		{
+			qglDepthMask( GL_FALSE );
+		}
+	}
+
+	//
+	// fill/line mode
+	//
+	if ( diff & GLS_POLYMODE_LINE )
+	{
+		if ( stateBits & GLS_POLYMODE_LINE )
+		{
+			qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
+		}
+		else
+		{
+			qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+		}
+	}
+
+	//
+	// depthtest
+	//
+	if ( diff & GLS_DEPTHTEST_DISABLE )
+	{
+		if ( stateBits & GLS_DEPTHTEST_DISABLE )
+		{
+			qglDisable( GL_DEPTH_TEST );
+		}
+		else
+		{
+			qglEnable( GL_DEPTH_TEST );
+		}
+	}
+
+	//
+	// alpha test
+	//
+	if ( diff & GLS_ATEST_BITS )
+	{
+		switch ( stateBits & GLS_ATEST_BITS )
+		{
+		case 0:
+			qglDisable( GL_ALPHA_TEST );
+			break;
+		case GLS_ATEST_GT_0:
+			qglEnable( GL_ALPHA_TEST );
+			qglAlphaFunc( GL_GREATER, 0.0f );
+			break;
+		case GLS_ATEST_LT_80:
+			qglEnable( GL_ALPHA_TEST );
+			qglAlphaFunc( GL_LESS, 0.5f );
+			break;
+		case GLS_ATEST_GE_80:
+			qglEnable( GL_ALPHA_TEST );
+			qglAlphaFunc( GL_GEQUAL, 0.5f );
+			break;
+		default:
+			assert( 0 );
+			break;
+		}
+	}
+
+	glState.glStateBits = stateBits;
+}
+
+
+void GL_SetProjectionMatrix(matrix_t matrix)
+{
+	Matrix16Copy(matrix, glState.projection);
+	Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection);	
+}
+
+
+void GL_SetModelviewMatrix(matrix_t matrix)
+{
+	Matrix16Copy(matrix, glState.modelview);
+	Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection);	
+}
+
+
+/*
+================
+RB_Hyperspace
+
+A player has predicted a teleport, but hasn't arrived yet
+================
+*/
+static void RB_Hyperspace( void ) {
+	float		c;
+
+	if ( !backEnd.isHyperspace ) {
+		// do initialization shit
+	}
+
+	c = ( backEnd.refdef.time & 255 ) / 255.0f;
+	qglClearColor( c, c, c, 1 );
+	qglClear( GL_COLOR_BUFFER_BIT );
+
+	backEnd.isHyperspace = qtrue;
+}
+
+
+static void SetViewportAndScissor( void ) {
+	GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
+
+	// set the window clipping
+	qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, 
+		backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
+	qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, 
+		backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
+}
+
+/*
+=================
+RB_BeginDrawingView
+
+Any mirrored or portaled views have already been drawn, so prepare
+to actually render the visible surfaces for this view
+=================
+*/
+void RB_BeginDrawingView (void) {
+	int clearBits = 0;
+
+	// sync with gl if needed
+	if ( r_finish->integer == 1 && !glState.finishCalled ) {
+		qglFinish ();
+		glState.finishCalled = qtrue;
+	}
+	if ( r_finish->integer == 0 ) {
+		glState.finishCalled = qtrue;
+	}
+
+	// we will need to change the projection matrix before drawing
+	// 2D images again
+	backEnd.projection2D = qfalse;
+
+	if (glRefConfig.framebufferObject)
+	{
+		// FIXME: HUGE HACK: render to the screen fbo if we've already postprocessed the frame and aren't drawing more world
+		if (backEnd.viewParms.targetFbo == tr.renderFbo && backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL))
+		{
+			FBO_Bind(tr.screenScratchFbo);
+		}
+		else
+		{
+			FBO_Bind(backEnd.viewParms.targetFbo);
+		}
+	}
+
+	//
+	// set the modelview matrix for the viewer
+	//
+	SetViewportAndScissor();
+
+	// ensures that depth writes are enabled for the depth clear
+	GL_State( GLS_DEFAULT );
+	// clear relevant buffers
+	clearBits = GL_DEPTH_BUFFER_BIT;
+
+	if ( r_measureOverdraw->integer || r_shadows->integer == 2 )
+	{
+		clearBits |= GL_STENCIL_BUFFER_BIT;
+	}
+	if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) )
+	{
+		clearBits |= GL_COLOR_BUFFER_BIT;	// FIXME: only if sky shaders have been used
+#ifdef _DEBUG
+		qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f );	// FIXME: get color of sky
+#else
+		qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );	// FIXME: get color of sky
+#endif
+	}
+
+	// clear to white for shadow maps
+	if (backEnd.viewParms.flags & VPF_SHADOWMAP)
+	{
+		clearBits |= GL_COLOR_BUFFER_BIT;
+		qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
+	}
+
+	qglClear( clearBits );
+
+	if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) )
+	{
+		RB_Hyperspace();
+		return;
+	}
+	else
+	{
+		backEnd.isHyperspace = qfalse;
+	}
+
+	glState.faceCulling = -1;		// force face culling to set next time
+
+	// we will only draw a sun if there was sky rendered in this view
+	backEnd.skyRenderedThisView = qfalse;
+
+#ifdef REACTION
+	backEnd.viewHasSunFlare = qfalse;
+#endif
+
+	// clip to the plane of the portal
+	if ( backEnd.viewParms.isPortal ) {
+#if 0
+		float	plane[4];
+		double	plane2[4];
+
+		plane[0] = backEnd.viewParms.portalPlane.normal[0];
+		plane[1] = backEnd.viewParms.portalPlane.normal[1];
+		plane[2] = backEnd.viewParms.portalPlane.normal[2];
+		plane[3] = backEnd.viewParms.portalPlane.dist;
+
+		plane2[0] = DotProduct (backEnd.viewParms.or.axis[0], plane);
+		plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane);
+		plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane);
+		plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3];
+#endif
+		GL_SetModelviewMatrix( s_flipMatrix );
+	}
+}
+
+
+#define	MAC_EVENT_PUMP_MSEC		5
+
+/*
+==================
+RB_RenderDrawSurfList
+==================
+*/
+void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+	shader_t		*shader, *oldShader;
+	int				fogNum, oldFogNum;
+	int				entityNum, oldEntityNum;
+	int				dlighted, oldDlighted;
+	int				pshadowed, oldPshadowed;
+	qboolean		depthRange, oldDepthRange, isCrosshair, wasCrosshair;
+	int				i;
+	drawSurf_t		*drawSurf;
+	int				oldSort;
+	float			originalTime;
+	FBO_t*			fbo = NULL;
+	qboolean		inQuery = qfalse;
+
+#if 1 //def REACTION
+	float			depth[2];
+#endif
+
+
+	// save original time for entity shader offsets
+	originalTime = backEnd.refdef.floatTime;
+
+	fbo = glState.currentFBO;
+
+	// draw everything
+	oldEntityNum = -1;
+	backEnd.currentEntity = &tr.worldEntity;
+	oldShader = NULL;
+	oldFogNum = -1;
+	oldDepthRange = qfalse;
+	wasCrosshair = qfalse;
+	oldDlighted = qfalse;
+	oldPshadowed = qfalse;
+	oldSort = -1;
+	depthRange = qfalse;
+
+#if 1 //def REACTION
+	depth[0] = 0.f;
+	depth[1] = 1.f;
+#endif
+
+	backEnd.pc.c_surfaces += numDrawSurfs;
+
+	for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) {
+		if ( drawSurf->sort == oldSort ) {
+			if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
+				continue;
+
+			// fast path, same as previous sort
+			rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+			continue;
+		}
+		oldSort = drawSurf->sort;
+		R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed );
+
+		//
+		// change the tess parameters if needed
+		// a "entityMergable" shader is a shader that can have surfaces from seperate
+		// entities merged into a single batch, like smoke and blood puff sprites
+		if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted || pshadowed != oldPshadowed
+			|| ( entityNum != oldEntityNum && !shader->entityMergable ) ) {
+			if (oldShader != NULL) {
+				RB_EndSurface();
+			}
+			RB_BeginSurface( shader, fogNum );
+			backEnd.pc.c_surfBatches++;
+			oldShader = shader;
+			oldFogNum = fogNum;
+			oldDlighted = dlighted;
+			oldPshadowed = pshadowed;
+		}
+
+		if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
+			continue;
+
+		//
+		// change the modelview matrix if needed
+		//
+		if ( entityNum != oldEntityNum ) {
+			qboolean sunflare = qfalse;
+			depthRange = isCrosshair = qfalse;
+
+#ifdef REACTION
+			// if we were rendering to a FBO and the previous entity was a sunflare
+			// and the current one isn't, switch back to the main fbo
+			if (oldEntityNum != -1 && fbo && !backEnd.depthFill &&
+				RF_SUNFLARE == (backEnd.refdef.entities[oldEntityNum].e.renderfx & RF_SUNFLARE) &&
+				0 == (backEnd.refdef.entities[entityNum].e.renderfx & RF_SUNFLARE))
+			{
+				if (inQuery) {
+					inQuery = qfalse;
+					qglEndQueryARB(GL_SAMPLES_PASSED_ARB);
+				}
+				FBO_Bind(fbo);
+				qglDepthRange(depth[0], depth[1]);
+			}
+#endif
+
+			if ( entityNum != REFENTITYNUM_WORLD ) {
+				backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
+				backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime;
+				// we have to reset the shaderTime as well otherwise image animations start
+				// from the wrong frame
+				tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+
+				// set up the transformation matrix
+				R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.or );
+
+				// set up the dynamic lighting if needed
+				if ( backEnd.currentEntity->needDlights ) {
+					R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
+				}
+
+#ifdef REACTION
+				// if the current entity is a sunflare
+				if(backEnd.currentEntity->e.renderfx & RF_SUNFLARE && !backEnd.depthFill) {
+					// if we're rendering to a fbo
+					if (fbo) {
+						VectorCopy(backEnd.currentEntity->e.origin, backEnd.sunFlarePos);
+						// switch FBO
+						FBO_Bind(tr.godRaysFbo);
+
+						qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+						qglClear( GL_COLOR_BUFFER_BIT );
+
+						qglDepthRange(1.f, 1.f);
+						if (glRefConfig.occlusionQuery && !inQuery && !backEnd.viewHasSunFlare) {
+							inQuery = qtrue;
+							tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue;
+							qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
+						}
+						sunflare = qtrue;
+					} else {
+						depthRange = qtrue;
+					}
+				}
+#endif
+
+				if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK)
+				{
+					// hack the depth range to prevent view model from poking into walls
+					depthRange = qtrue;
+					
+					if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR)
+						isCrosshair = qtrue;
+				}
+			} else {
+				backEnd.currentEntity = &tr.worldEntity;
+				backEnd.refdef.floatTime = originalTime;
+				backEnd.or = backEnd.viewParms.world;
+				// we have to reset the shaderTime as well otherwise image animations on
+				// the world (like water) continue with the wrong frame
+				tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+				R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
+			}
+
+			GL_SetModelviewMatrix( backEnd.or.modelMatrix );
+
+			//
+			// change depthrange. Also change projection matrix so first person weapon does not look like coming
+			// out of the screen.
+			//
+			if (oldDepthRange != depthRange || wasCrosshair != isCrosshair)
+			{
+				if (depthRange)
+				{
+					if(backEnd.viewParms.stereoFrame != STEREO_CENTER)
+					{
+						if(isCrosshair)
+						{
+							if(oldDepthRange)
+							{
+								// was not a crosshair but now is, change back proj matrix
+								GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
+							}
+						}
+						else
+						{
+							viewParms_t temp = backEnd.viewParms;
+
+							R_SetupProjection(&temp, r_znear->value, 0, qfalse);
+
+							GL_SetProjectionMatrix( temp.projectionMatrix );
+						}
+					}
+
+#if 1 //def REACTION
+ 					if(!oldDepthRange)
+					{
+						depth[0] = 0;
+						depth[1] = 0.3f;
+ 						qglDepthRange (0, 0.3);
+	 				}
+#endif
+				}
+				else
+				{
+					if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER)
+					{
+						GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
+					}
+
+					if (!sunflare)
+						qglDepthRange (0, 1);
+#if 1 //def REACTION
+					depth[0] = 0;
+					depth[1] = 1;
+#endif
+				}
+
+				oldDepthRange = depthRange;
+				wasCrosshair = isCrosshair;
+			}
+
+			oldEntityNum = entityNum;
+		}
+
+		// add the triangles for this surface
+		rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+	}
+
+	backEnd.refdef.floatTime = originalTime;
+
+	// draw the contents of the last shader batch
+	if (oldShader != NULL) {
+		RB_EndSurface();
+	}
+
+	if (inQuery) {
+		inQuery = qfalse;
+		qglEndQueryARB(GL_SAMPLES_PASSED_ARB);
+	}
+#ifdef REACTION
+	// HACK: flip Z and render black to god rays buffer
+	if (backEnd.frameHasSunFlare && !backEnd.depthFill)
+	{
+		vec4_t black;
+		VectorSet4(black, 0, 0, 0, 1);
+		qglDepthRange (1, 1);
+		FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, tr.godRaysFbo, NULL, NULL, black, GLS_DEPTHFUNC_GREATER);
+	}
+#endif
+
+	FBO_Bind(fbo);
+
+	// go back to the world modelview matrix
+
+	GL_SetModelviewMatrix( backEnd.viewParms.world.modelMatrix );
+	//if ( depthRange ) {
+		qglDepthRange (0, 1);
+	//}
+}
+
+
+/*
+============================================================================
+
+RENDER BACK END THREAD FUNCTIONS
+
+============================================================================
+*/
+
+/*
+================
+RB_SetGL2D
+
+================
+*/
+void	RB_SetGL2D (void) {
+	matrix_t matrix;
+	int width, height;
+
+	if (backEnd.projection2D && backEnd.last2DFBO == glState.currentFBO)
+		return;
+
+	backEnd.projection2D = qtrue;
+	backEnd.last2DFBO = glState.currentFBO;
+
+	if (glState.currentFBO)
+	{
+		width = glState.currentFBO->width;
+		height = glState.currentFBO->height;
+	}
+	else
+	{
+		width = glConfig.vidWidth;
+		height = glConfig.vidHeight;
+	}
+
+	// set 2D virtual screen size
+	qglViewport( 0, 0, width, height );
+	qglScissor( 0, 0, width, height );
+
+	Matrix16Ortho(0, width, height, 0, 0, 1, matrix);
+	GL_SetProjectionMatrix(matrix);
+	Matrix16Identity(matrix);
+	GL_SetModelviewMatrix(matrix);
+
+	GL_State( GLS_DEPTHTEST_DISABLE |
+			  GLS_SRCBLEND_SRC_ALPHA |
+			  GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
+
+	qglDisable( GL_CULL_FACE );
+	qglDisable( GL_CLIP_PLANE0 );
+
+	// set time for 2D shaders
+	backEnd.refdef.time = ri.Milliseconds();
+	backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f;
+
+	// reset color scaling
+	backEnd.refdef.colorScale = 1.0f;
+}
+
+
+/*
+=============
+RE_StretchRaw
+
+FIXME: not exactly backend
+Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle.
+Used for cinematics.
+=============
+*/
+void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
+	int			i, j;
+	int			start, end;
+	shaderProgram_t *sp = &tr.textureColorShader;
+	vec4_t color;
+
+	if ( !tr.registered ) {
+		return;
+	}
+	R_SyncRenderThread();
+
+	// we definately want to sync every frame for the cinematics
+	qglFinish();
+
+	start = 0;
+	if ( r_speeds->integer ) {
+		start = ri.Milliseconds();
+	}
+
+	// make sure rows and cols are powers of 2
+	for ( i = 0 ; ( 1 << i ) < cols ; i++ ) {
+	}
+	for ( j = 0 ; ( 1 << j ) < rows ; j++ ) {
+	}
+	if ( ( 1 << i ) != cols || ( 1 << j ) != rows) {
+		ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows);
+	}
+
+	GL_Bind( tr.scratchImage[client] );
+
+	// if the scratchImage isn't in the format we want, specify it as a new texture
+	if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
+		tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
+		tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows;
+		qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );	
+	} else {
+		if (dirty) {
+			// otherwise, just subimage upload it so that drivers can tell we are going to be changing
+			// it and don't try and do a texture compression
+			qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
+		}
+	}
+
+	if ( r_speeds->integer ) {
+		end = ri.Milliseconds();
+		ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start );
+	}
+
+	// FIXME: HUGE hack
+	if (glRefConfig.framebufferObject && !glState.currentFBO)
+	{
+		if (backEnd.framePostProcessed)
+		{
+			FBO_Bind(tr.screenScratchFbo);
+		}
+		else
+		{
+			FBO_Bind(tr.renderFbo);
+		}
+	}
+
+	RB_SetGL2D();
+
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+
+	tess.xyz[tess.numVertexes][0] = x;
+	tess.xyz[tess.numVertexes][1] = y;
+	tess.xyz[tess.numVertexes][2] = 0;
+	tess.xyz[tess.numVertexes][3] = 1;
+	tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols;
+	tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows;
+	tess.texCoords[tess.numVertexes][1][0] = 0;
+	tess.texCoords[tess.numVertexes][1][1] = 1;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = x + w;
+	tess.xyz[tess.numVertexes][1] = y;
+	tess.xyz[tess.numVertexes][2] = 0;
+	tess.xyz[tess.numVertexes][3] = 1;
+	tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols;
+	tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows;
+	tess.texCoords[tess.numVertexes][1][0] = 0;
+	tess.texCoords[tess.numVertexes][1][1] = 1;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = x + w;
+	tess.xyz[tess.numVertexes][1] = y + h;
+	tess.xyz[tess.numVertexes][2] = 0;
+	tess.xyz[tess.numVertexes][3] = 1;
+	tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols;
+	tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows;
+	tess.texCoords[tess.numVertexes][1][0] = 0;
+	tess.texCoords[tess.numVertexes][1][1] = 1;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = x;
+	tess.xyz[tess.numVertexes][1] = y + h;
+	tess.xyz[tess.numVertexes][2] = 0;
+	tess.xyz[tess.numVertexes][3] = 1;
+	tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols;
+	tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows;
+	tess.texCoords[tess.numVertexes][1][0] = 0;
+	tess.texCoords[tess.numVertexes][1][1] = 1;
+	tess.numVertexes++;
+
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 1;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 3;
+
+	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
+	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+	
+	sp = &tr.textureColorShader;
+
+	GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
+	
+	GLSL_BindProgram(sp);
+
+	GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+	VectorSet4(color, 1, 1, 1, 1);
+	GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color);
+
+	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex);
+	
+	//R_BindNullVBO();
+	//R_BindNullIBO();
+
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+}
+
+void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
+
+	GL_Bind( tr.scratchImage[client] );
+
+	// if the scratchImage isn't in the format we want, specify it as a new texture
+	if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
+		tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
+		tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows;
+		qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );	
+	} else {
+		if (dirty) {
+			// otherwise, just subimage upload it so that drivers can tell we are going to be changing
+			// it and don't try and do a texture compression
+			qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
+		}
+	}
+}
+
+
+/*
+=============
+RB_SetColor
+
+=============
+*/
+const void	*RB_SetColor( const void *data ) {
+	const setColorCommand_t	*cmd;
+
+	cmd = (const setColorCommand_t *)data;
+
+	backEnd.color2D[0] = cmd->color[0] * 255;
+	backEnd.color2D[1] = cmd->color[1] * 255;
+	backEnd.color2D[2] = cmd->color[2] * 255;
+	backEnd.color2D[3] = cmd->color[3] * 255;
+
+	return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_StretchPic
+=============
+*/
+const void *RB_StretchPic ( const void *data ) {
+	const stretchPicCommand_t	*cmd;
+	shader_t *shader;
+	int		numVerts, numIndexes;
+
+	cmd = (const stretchPicCommand_t *)data;
+
+	// FIXME: HUGE hack
+	if (glRefConfig.framebufferObject && !glState.currentFBO)
+	{
+		if (backEnd.framePostProcessed)
+		{
+			FBO_Bind(tr.screenScratchFbo);
+		}
+		else
+		{
+			FBO_Bind(tr.renderFbo);
+		}
+	}
+
+	RB_SetGL2D();
+
+	shader = cmd->shader;
+	if ( shader != tess.shader ) {
+		if ( tess.numIndexes ) {
+			RB_EndSurface();
+		}
+		backEnd.currentEntity = &backEnd.entity2D;
+		RB_BeginSurface( shader, 0 );
+	}
+
+	RB_CHECKOVERFLOW( 4, 6 );
+	numVerts = tess.numVertexes;
+	numIndexes = tess.numIndexes;
+
+	tess.numVertexes += 4;
+	tess.numIndexes += 6;
+
+	tess.indexes[ numIndexes ] = numVerts + 3;
+	tess.indexes[ numIndexes + 1 ] = numVerts + 0;
+	tess.indexes[ numIndexes + 2 ] = numVerts + 2;
+	tess.indexes[ numIndexes + 3 ] = numVerts + 2;
+	tess.indexes[ numIndexes + 4 ] = numVerts + 0;
+	tess.indexes[ numIndexes + 5 ] = numVerts + 1;
+
+	{
+		vec4_t color;
+
+		VectorScale4(backEnd.color2D, 1.0f / 255.0f, color);
+
+		VectorCopy4(color, tess.vertexColors[ numVerts ]);
+		VectorCopy4(color, tess.vertexColors[ numVerts + 1]);
+		VectorCopy4(color, tess.vertexColors[ numVerts + 2]);
+		VectorCopy4(color, tess.vertexColors[ numVerts + 3 ]);
+	}
+
+	tess.xyz[ numVerts ][0] = cmd->x;
+	tess.xyz[ numVerts ][1] = cmd->y;
+	tess.xyz[ numVerts ][2] = 0;
+
+	tess.texCoords[ numVerts ][0][0] = cmd->s1;
+	tess.texCoords[ numVerts ][0][1] = cmd->t1;
+
+	tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w;
+	tess.xyz[ numVerts + 1 ][1] = cmd->y;
+	tess.xyz[ numVerts + 1 ][2] = 0;
+
+	tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2;
+	tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1;
+
+	tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w;
+	tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h;
+	tess.xyz[ numVerts + 2 ][2] = 0;
+
+	tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2;
+	tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2;
+
+	tess.xyz[ numVerts + 3 ][0] = cmd->x;
+	tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h;
+	tess.xyz[ numVerts + 3 ][2] = 0;
+
+	tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1;
+	tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2;
+
+	return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawSurfs
+
+=============
+*/
+const void	*RB_DrawSurfs( const void *data ) {
+	const drawSurfsCommand_t	*cmd;
+
+	// finish any 2D drawing if needed
+	if ( tess.numIndexes ) {
+		RB_EndSurface();
+	}
+
+	cmd = (const drawSurfsCommand_t *)data;
+
+	backEnd.refdef = cmd->refdef;
+	backEnd.viewParms = cmd->viewParms;
+
+	// clear the z buffer, set the modelview, etc
+	RB_BeginDrawingView ();
+
+	if ((backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
+	{
+		qglEnable(GL_DEPTH_CLAMP);
+	}
+
+	if (!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || (backEnd.viewParms.flags & VPF_DEPTHSHADOW)))
+	{
+		FBO_t *oldFbo = glState.currentFBO;
+
+		backEnd.depthFill = qtrue;
+		qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+		RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
+		qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]);
+		backEnd.depthFill = qfalse;
+
+		// If we're using multisampling, resolve the depth first
+		if (tr.msaaResolveFbo)
+		{
+			FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+		}
+
+		if (r_ssao->integer)
+		{			
+			vec2_t srcTexScale;
+			vec4_t color;
+			vec4_t quadVerts[4];
+			vec2_t texCoords[4];
+			vec2_t invTexRes;
+			
+			matrix_t idmatrix;
+
+			srcTexScale[0] = srcTexScale[1] = 1.0f;
+			color[0] = color[1] = color[2] = color[3] = 1.0f;
+
+			FBO_Bind(tr.hdrDepthFbo);
+
+			qglViewport(0, 0, tr.hdrDepthFbo->width, tr.hdrDepthFbo->height);
+			qglScissor(0, 0, tr.hdrDepthFbo->width, tr.hdrDepthFbo->height);
+
+			Matrix16Identity(idmatrix);
+
+			VectorSet4(quadVerts[0], -1,  1, 0, 1);
+			VectorSet4(quadVerts[1],  1,  1, 0, 1);
+			VectorSet4(quadVerts[2],  1, -1, 0, 1);
+			VectorSet4(quadVerts[3], -1, -1, 0, 1);
+
+			texCoords[0][0] = 0; texCoords[0][1] = 1;
+			texCoords[1][0] = 1; texCoords[1][1] = 1;
+			texCoords[2][0] = 1; texCoords[2][1] = 0;
+			texCoords[3][0] = 0; texCoords[3][1] = 0;
+
+			invTexRes[0] = 0.0f;
+			invTexRes[1] = 0.0f;
+
+			GL_State( GLS_DEPTHTEST_DISABLE );
+
+			GLSL_BindProgram(&tr.textureColorShader);
+			
+			GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP);
+
+			GLSL_SetUniformMatrix16(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, idmatrix);
+			GLSL_SetUniformVec4(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR, color);
+			GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes);
+			GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax);
+			GLSL_SetUniformVec3(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear);
+
+			RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+		}
+
+		if (backEnd.viewParms.flags & VPF_USESUNLIGHT)
+		{
+			vec4_t quadVerts[4];
+			vec2_t texCoords[4];
+
+			FBO_Bind(tr.screenShadowFbo);
+
+			qglViewport(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height);
+			qglScissor(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height);
+
+			VectorSet4(quadVerts[0], -1,  1, 0, 1);
+			VectorSet4(quadVerts[1],  1,  1, 0, 1);
+			VectorSet4(quadVerts[2],  1, -1, 0, 1);
+			VectorSet4(quadVerts[3], -1, -1, 0, 1);
+
+			texCoords[0][0] = 0; texCoords[0][1] = 1;
+			texCoords[1][0] = 1; texCoords[1][1] = 1;
+			texCoords[2][0] = 1; texCoords[2][1] = 0;
+			texCoords[3][0] = 0; texCoords[3][1] = 0;
+
+			GL_State( GLS_DEPTHTEST_DISABLE );
+
+			GLSL_BindProgram(&tr.shadowmaskShader);
+
+			GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP);
+			GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP);
+			GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2);
+			GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3);
+
+			GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP,  backEnd.refdef.sunShadowMvp[0]);
+			GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]);
+			GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]);
+			
+			GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWORIGIN,  backEnd.refdef.vieworg);
+			{
+				vec4_t viewInfo;
+				vec3_t viewVector;
+
+				float zmax = backEnd.viewParms.zFar;
+				float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f);
+				float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f);
+
+				float zmin = r_znear->value;
+
+				VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector);
+				GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWFORWARD, viewVector);
+				VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector);
+				GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWLEFT,    viewVector);
+				VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector);
+				GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWUP,      viewVector);
+
+				VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
+
+				GLSL_SetUniformVec4(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWINFO, viewInfo);
+			}
+
+
+			RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+		}
+
+		if (r_ssao->integer)
+		{
+			vec4_t quadVerts[4];
+			vec2_t texCoords[4];
+
+			FBO_Bind(tr.quarterFbo[0]);
+
+			qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+			qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+
+			VectorSet4(quadVerts[0], -1,  1, 0, 1);
+			VectorSet4(quadVerts[1],  1,  1, 0, 1);
+			VectorSet4(quadVerts[2],  1, -1, 0, 1);
+			VectorSet4(quadVerts[3], -1, -1, 0, 1);
+
+			texCoords[0][0] = 0; texCoords[0][1] = 1;
+			texCoords[1][0] = 1; texCoords[1][1] = 1;
+			texCoords[2][0] = 1; texCoords[2][1] = 0;
+			texCoords[3][0] = 0; texCoords[3][1] = 0;
+
+			GL_State( GLS_DEPTHTEST_DISABLE );
+
+			GLSL_BindProgram(&tr.ssaoShader);
+
+			GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP);
+
+			{
+				vec4_t viewInfo;
+
+				float zmax = backEnd.viewParms.zFar;
+				float zmin = r_znear->value;
+
+				VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
+
+				GLSL_SetUniformVec4(&tr.ssaoShader, SSAO_UNIFORM_VIEWINFO, viewInfo);
+			}
+
+			RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+
+
+			FBO_Bind(tr.quarterFbo[1]);
+
+			qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height);
+			qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height);
+
+			GLSL_BindProgram(&tr.depthBlurShader[0]);
+
+			GL_BindToTMU(tr.quarterImage[0],  TB_COLORMAP);
+			GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
+
+			{
+				vec4_t viewInfo;
+
+				float zmax = backEnd.viewParms.zFar;
+				float zmin = r_znear->value;
+
+				VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
+
+				GLSL_SetUniformVec4(&tr.depthBlurShader[0], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo);
+			}
+
+			RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+
+
+			FBO_Bind(tr.screenSsaoFbo);
+
+			qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height);
+			qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height);
+
+			GLSL_BindProgram(&tr.depthBlurShader[1]);
+
+			GL_BindToTMU(tr.quarterImage[1],  TB_COLORMAP);
+			GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
+
+			{
+				vec4_t viewInfo;
+
+				float zmax = backEnd.viewParms.zFar;
+				float zmin = r_znear->value;
+
+				VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
+
+				GLSL_SetUniformVec4(&tr.depthBlurShader[1], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo);
+			}
+
+
+			RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+		}
+
+		// reset viewport and scissor
+		FBO_Bind(oldFbo);
+		SetViewportAndScissor();
+	}
+
+	if ((backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
+	{
+		qglDisable(GL_DEPTH_CLAMP);
+	}
+
+	if (!(backEnd.viewParms.flags & VPF_DEPTHSHADOW))
+	{
+		RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
+
+#if 0
+		RB_DrawSun();
+#endif
+		// darken down any stencil shadows
+		RB_ShadowFinish();		
+
+		// add light flares on lights that aren't obscured
+		RB_RenderFlares();
+	}
+
+	if (glRefConfig.framebufferObject)
+		FBO_Bind(NULL);
+
+	return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawBuffer
+
+=============
+*/
+const void	*RB_DrawBuffer( const void *data ) {
+	const drawBufferCommand_t	*cmd;
+
+	cmd = (const drawBufferCommand_t *)data;
+
+	qglDrawBuffer( cmd->buffer );
+
+	// clear screen for debugging
+	if ( r_clear->integer ) {
+		qglClearColor( 1, 0, 0.5, 1 );
+		qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+	}
+
+	return (const void *)(cmd + 1);
+}
+
+/*
+===============
+RB_ShowImages
+
+Draw all the images to the screen, on top of whatever
+was there.  This is used to test for texture thrashing.
+
+Also called by RE_EndRegistration
+===============
+*/
+void RB_ShowImages( void ) {
+	int		i;
+	image_t	*image;
+	float	x, y, w, h;
+	int		start, end;
+
+	RB_SetGL2D();
+
+	qglClear( GL_COLOR_BUFFER_BIT );
+
+	qglFinish();
+
+	start = ri.Milliseconds();
+
+	for ( i=0 ; i<tr.numImages ; i++ ) {
+		image = tr.images[i];
+
+		w = glConfig.vidWidth / 20;
+		h = glConfig.vidHeight / 15;
+		x = i % 20 * w;
+		y = i / 20 * h;
+
+		// show in proportional size in mode 2
+		if ( r_showImages->integer == 2 ) {
+			w *= image->uploadWidth / 512.0f;
+			h *= image->uploadHeight / 512.0f;
+		}
+
+		{
+			vec4_t quadVerts[4];
+
+			GL_Bind(image);
+
+			VectorSet4(quadVerts[0], x, y, 0, 1);
+			VectorSet4(quadVerts[1], x + w, y, 0, 1);
+			VectorSet4(quadVerts[2], x + w, y + h, 0, 1);
+			VectorSet4(quadVerts[3], x, y + h, 0, 1);
+
+			RB_InstantQuad(quadVerts);
+		}
+	}
+
+	qglFinish();
+
+	end = ri.Milliseconds();
+	ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start );
+
+}
+
+/*
+=============
+RB_ColorMask
+
+=============
+*/
+const void *RB_ColorMask(const void *data)
+{
+	const colorMaskCommand_t *cmd = data;
+
+	if (glRefConfig.framebufferObject)
+	{
+		// reverse color mask, so 0 0 0 0 is the default
+		backEnd.colorMask[0] = !cmd->rgba[0];
+		backEnd.colorMask[1] = !cmd->rgba[1];
+		backEnd.colorMask[2] = !cmd->rgba[2];
+		backEnd.colorMask[3] = !cmd->rgba[3];
+	}
+
+	qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]);
+	
+	return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_ClearDepth
+
+=============
+*/
+const void *RB_ClearDepth(const void *data)
+{
+	const clearDepthCommand_t *cmd = data;
+	
+	if(tess.numIndexes)
+		RB_EndSurface();
+
+	// texture swapping test
+	if (r_showImages->integer)
+		RB_ShowImages();
+
+	if (backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL))
+	{
+		FBO_Bind(tr.screenScratchFbo);
+	}
+	else
+	{
+		FBO_Bind(tr.renderFbo);
+	}
+	qglClear(GL_DEPTH_BUFFER_BIT);
+
+	// if we're doing MSAA, clear the depth texture for the resolve buffer
+	if (tr.msaaResolveFbo)
+	{
+		FBO_Bind(tr.screenScratchFbo);
+		qglClear(GL_DEPTH_BUFFER_BIT);
+	}
+
+	
+	return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_SwapBuffers
+
+=============
+*/
+const void	*RB_SwapBuffers( const void *data ) {
+	const swapBuffersCommand_t	*cmd;
+
+	// finish any 2D drawing if needed
+	if ( tess.numIndexes ) {
+		RB_EndSurface();
+	}
+
+	// texture swapping test
+	if ( r_showImages->integer ) {
+		RB_ShowImages();
+	}
+
+	cmd = (const swapBuffersCommand_t *)data;
+
+	// we measure overdraw by reading back the stencil buffer and
+	// counting up the number of increments that have happened
+	if ( r_measureOverdraw->integer ) {
+		int i;
+		long sum = 0;
+		unsigned char *stencilReadback;
+
+		stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight );
+		qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );
+
+		for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) {
+			sum += stencilReadback[i];
+		}
+
+		backEnd.pc.c_overDraw += sum;
+		ri.Hunk_FreeTempMemory( stencilReadback );
+	}
+
+	if (glRefConfig.framebufferObject)
+	{
+		// copy final image to screen
+		vec4_t color;
+
+		if (backEnd.framePostProcessed)
+		{
+			// frame was postprocessed into screen fbo, copy from there
+		}
+		else if (!glRefConfig.framebuffer_srgb)
+		{
+			// Copy render to screenscratch, possibly resolving MSAA
+			FBO_FastBlit(tr.renderFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		}
+		else
+		{
+			FBO_t *srcFbo = tr.renderFbo;
+
+			if (tr.msaaResolveFbo)
+			{
+				// Resolve the MSAA before copying
+				FBO_FastBlit(srcFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+				srcFbo = tr.msaaResolveFbo;
+			}
+
+			// need to copy from resolve to screenscratch to fix gamma
+			FBO_Blit(srcFbo, NULL, NULL, tr.screenScratchFbo, NULL, NULL, NULL, 0);
+		}
+		
+		color[0] =
+		color[1] =
+		color[2] = pow(2, tr.overbrightBits); //exp2(tr.overbrightBits);
+		color[3] = 1.0f;
+
+		// turn off colormask when copying final image
+		if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3])
+			qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+			
+		FBO_Blit(tr.screenScratchFbo, NULL, NULL, NULL, NULL, NULL, color, 0);
+
+		if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3])
+			qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]);
+	}
+
+	if ( !glState.finishCalled ) {
+		qglFinish();
+	}
+
+	GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
+
+	GLimp_EndFrame();
+
+	backEnd.framePostProcessed = qfalse;
+	backEnd.projection2D = qfalse;
+#ifdef REACTION
+	backEnd.frameHasSunFlare = qfalse;
+#endif
+
+	return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_CapShadowMap
+
+=============
+*/
+const void *RB_CapShadowMap(const void *data)
+{
+	const capShadowmapCommand_t *cmd = data;
+
+	if (cmd->map != -1)
+	{
+		GL_SelectTexture(0);
+		if (cmd->cubeSide != -1)
+		{
+			GL_BindCubemap(tr.shadowCubemaps[cmd->map]);
+			qglCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cmd->cubeSide, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0);
+		}
+		else
+		{
+			GL_Bind(tr.pshadowMaps[cmd->map]);
+			qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0);
+		}
+	}
+
+	return (const void *)(cmd + 1);
+}
+
+
+
+/*
+=============
+RB_PostProcess
+
+=============
+*/
+const void *RB_PostProcess(const void *data)
+{
+	const postProcessCommand_t *cmd = data;
+	FBO_t *srcFbo;
+	qboolean autoExposure;
+
+	if (!glRefConfig.framebufferObject)
+	{
+		// do nothing
+		backEnd.framePostProcessed = qtrue;
+
+		return (const void *)(cmd + 1);
+	}
+
+	srcFbo = tr.renderFbo;
+	if (tr.msaaResolveFbo)
+	{
+		// Resolve the MSAA before anything else
+		FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+		srcFbo = tr.msaaResolveFbo;
+	}
+
+	if (r_postProcess->integer && r_ssao->integer)
+	{
+		vec4i_t dstBox;
+		VectorSet4(dstBox, 0, 0, srcFbo->width, srcFbo->height);
+		FBO_BlitFromTexture(tr.screenSsaoImage, NULL, NULL, srcFbo, dstBox, NULL, NULL, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
+	}
+
+	if (r_postProcess->integer && (r_toneMap->integer || r_forceToneMap->integer))
+	{
+		autoExposure = r_autoExposure->integer || r_forceAutoExposure;
+		RB_ToneMap(srcFbo, autoExposure);
+	}
+	else if (!glRefConfig.framebuffer_srgb && r_cameraExposure->value == 0.0f)
+	{
+		FBO_FastBlit(srcFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	}
+	else
+	{
+		vec4_t color;
+
+		color[0] =
+		color[1] =
+		color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value);
+		color[3] = 1.0f;
+
+		FBO_Blit(srcFbo, NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, 0);
+	}
+
+#ifdef REACTION
+	if (r_postProcess->integer && glRefConfig.framebufferObject)
+	{
+		RB_GodRays();
+
+		if (1)
+			RB_BokehBlur(backEnd.refdef.blurFactor);
+		else
+			RB_GaussianBlur(backEnd.refdef.blurFactor);
+	}
+#endif
+
+	if (0)
+	{
+		vec4i_t dstBox;
+		VectorSet4(dstBox, 0, 0, 128, 128);
+		FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+		VectorSet4(dstBox, 128, 0, 128, 128);
+		FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+		VectorSet4(dstBox, 256, 0, 128, 128);
+		FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+	}
+
+	if (0)
+	{
+		vec4i_t dstBox;
+		VectorSet4(dstBox, 256, tr.screenScratchFbo->height - 256, 256, 256);
+		FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+		VectorSet4(dstBox, 512, tr.screenScratchFbo->height - 256, 256, 256);
+		FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+	}
+
+	if (0)
+	{
+		vec4i_t dstBox;
+		VectorSet4(dstBox, 256, tr.screenScratchFbo->height - 256, 256, 256);
+		FBO_BlitFromTexture(tr.renderImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
+	}
+
+	backEnd.framePostProcessed = qtrue;
+
+	return (const void *)(cmd + 1);
+}
+
+/*
+====================
+RB_ExecuteRenderCommands
+
+This function will be called synchronously if running without
+smp extensions, or asynchronously by another thread.
+====================
+*/
+void RB_ExecuteRenderCommands( const void *data ) {
+	int		t1, t2;
+
+	t1 = ri.Milliseconds ();
+
+	if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) {
+		backEnd.smpFrame = 0;
+	} else {
+		backEnd.smpFrame = 1;
+	}
+
+	while ( 1 ) {
+		data = PADP(data, sizeof(void *));
+
+		switch ( *(const int *)data ) {
+		case RC_SET_COLOR:
+			data = RB_SetColor( data );
+			break;
+		case RC_STRETCH_PIC:
+			data = RB_StretchPic( data );
+			break;
+		case RC_DRAW_SURFS:
+			data = RB_DrawSurfs( data );
+			break;
+		case RC_DRAW_BUFFER:
+			data = RB_DrawBuffer( data );
+			break;
+		case RC_SWAP_BUFFERS:
+			data = RB_SwapBuffers( data );
+			break;
+		case RC_SCREENSHOT:
+			data = RB_TakeScreenshotCmd( data );
+			break;
+		case RC_VIDEOFRAME:
+			data = RB_TakeVideoFrameCmd( data );
+			break;
+		case RC_COLORMASK:
+			data = RB_ColorMask(data);
+			break;
+		case RC_CLEARDEPTH:
+			data = RB_ClearDepth(data);
+			break;
+		case RC_CAPSHADOWMAP:
+			data = RB_CapShadowMap(data);
+			break;
+		case RC_POSTPROCESS:
+			data = RB_PostProcess(data);
+			break;
+		case RC_END_OF_LIST:
+		default:
+			// stop rendering on this thread
+			t2 = ri.Milliseconds ();
+			backEnd.pc.msec = t2 - t1;
+			return;
+		}
+	}
+
+}
+
+
+/*
+================
+RB_RenderThread
+================
+*/
+void RB_RenderThread( void ) {
+	const void	*data;
+
+	// wait for either a rendering command or a quit command
+	while ( 1 ) {
+		// sleep until we have work to do
+		data = GLimp_RendererSleep();
+
+		if ( !data ) {
+			return;	// all done, renderer is shutting down
+		}
+
+		renderThreadActive = qtrue;
+
+		RB_ExecuteRenderCommands( data );
+
+		renderThreadActive = qfalse;
+	}
+}
+
+

Added: trunk/code/rend2/tr_bsp.c
===================================================================
--- trunk/code/rend2/tr_bsp.c	                        (rev 0)
+++ trunk/code/rend2/tr_bsp.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,3321 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_map.c
+
+#include "tr_local.h"
+
+/*
+
+Loads and prepares a map file for scene rendering.
+
+A single entry point:
+
+void RE_LoadWorldMap( const char *name );
+
+*/
+
+static	world_t		s_worldData;
+static	byte		*fileBase;
+
+int			c_subdivisions;
+int			c_gridVerts;
+
+//===============================================================================
+
+static void HSVtoRGB( float h, float s, float v, float rgb[3] )
+{
+	int i;
+	float f;
+	float p, q, t;
+
+	h *= 5;
+
+	i = floor( h );
+	f = h - i;
+
+	p = v * ( 1 - s );
+	q = v * ( 1 - s * f );
+	t = v * ( 1 - s * ( 1 - f ) );
+
+	switch ( i )
+	{
+	case 0:
+		rgb[0] = v;
+		rgb[1] = t;
+		rgb[2] = p;
+		break;
+	case 1:
+		rgb[0] = q;
+		rgb[1] = v;
+		rgb[2] = p;
+		break;
+	case 2:
+		rgb[0] = p;
+		rgb[1] = v;
+		rgb[2] = t;
+		break;
+	case 3:
+		rgb[0] = p;
+		rgb[1] = q;
+		rgb[2] = v;
+		break;
+	case 4:
+		rgb[0] = t;
+		rgb[1] = p;
+		rgb[2] = v;
+		break;
+	case 5:
+		rgb[0] = v;
+		rgb[1] = p;
+		rgb[2] = q;
+		break;
+	}
+}
+
+/*
+===============
+R_ColorShiftLightingBytes
+
+===============
+*/
+static	void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) {
+	int		shift, r, g, b;
+
+	// shift the color data based on overbright range
+	shift = r_mapOverBrightBits->integer - tr.overbrightBits;
+
+	// shift the data based on overbright range
+	r = in[0] << shift;
+	g = in[1] << shift;
+	b = in[2] << shift;
+	
+	// normalize by color instead of saturating to white
+	if ( ( r | g | b ) > 255 ) {
+		int		max;
+
+		max = r > g ? r : g;
+		max = max > b ? max : b;
+		r = r * 255 / max;
+		g = g * 255 / max;
+		b = b * 255 / max;
+	}
+
+	out[0] = r;
+	out[1] = g;
+	out[2] = b;
+	out[3] = in[3];
+}
+
+
+/*
+===============
+R_ColorShiftLightingBytes
+
+===============
+*/
+static void R_ColorShiftLightingFloats(float in[4], float out[4], float scale )
+{
+	scale *= pow(2.0f, r_mapOverBrightBits->integer - tr.overbrightBits);
+
+	out[0] = in[0] * scale;
+	out[1] = in[1] * scale;
+	out[2] = in[2] * scale;
+	out[3] = in[3];
+}
+
+
+void ColorToRGBE(const vec3_t color, unsigned char rgbe[4])
+{
+	vec3_t          sample;
+	float			maxComponent;
+	int				e;
+
+	VectorCopy(color, sample);
+
+	maxComponent = sample[0];
+	if(sample[1] > maxComponent)
+		maxComponent = sample[1];
+	if(sample[2] > maxComponent)
+		maxComponent = sample[2];
+
+	if(maxComponent < 1e-32)
+	{
+		rgbe[0] = 0;
+		rgbe[1] = 0;
+		rgbe[2] = 0;
+		rgbe[3] = 0;
+	}
+	else
+	{
+#if 0
+		maxComponent = frexp(maxComponent, &e) * 255.0 / maxComponent;
+		rgbe[0] = (unsigned char) (sample[0] * maxComponent);
+		rgbe[1] = (unsigned char) (sample[1] * maxComponent);
+		rgbe[2] = (unsigned char) (sample[2] * maxComponent);
+		rgbe[3] = (unsigned char) (e + 128);
+#else
+		e = ceil(log(maxComponent) / log(2.0f));//ceil(log2(maxComponent));
+		VectorScale(sample, 1.0 / pow(2.0f, e)/*exp2(e)*/, sample);
+
+		rgbe[0] = (unsigned char) (sample[0] * 255);
+		rgbe[1] = (unsigned char) (sample[1] * 255);
+		rgbe[2] = (unsigned char) (sample[2] * 255);
+		rgbe[3] = (unsigned char) (e + 128);
+#endif
+	}
+}
+
+
+void ColorToRGBA16F(const vec3_t color, unsigned short rgba16f[4])
+{
+	rgba16f[0] = FloatToHalf(color[0]);
+	rgba16f[1] = FloatToHalf(color[1]);
+	rgba16f[2] = FloatToHalf(color[2]);
+	rgba16f[3] = FloatToHalf(1.0f);
+}
+
+
+/*
+===============
+R_LoadLightmaps
+
+===============
+*/
+#define	DEFAULT_LIGHTMAP_SIZE	128
+#define MAX_LIGHTMAP_PAGES 2
+static	void R_LoadLightmaps( lump_t *l, lump_t *surfs ) {
+	byte		*buf, *buf_p;
+	dsurface_t  *surf;
+	int			len;
+	byte		*image;
+	int			i, j, numLightmaps, textureInternalFormat = 0;
+	float maxIntensity = 0;
+	double sumIntensity = 0;
+
+	len = l->filelen;
+	if ( !len ) {
+		return;
+	}
+	buf = fileBase + l->fileofs;
+
+	// we are about to upload textures
+	R_SyncRenderThread();
+
+	tr.lightmapSize = DEFAULT_LIGHTMAP_SIZE;
+	numLightmaps = len / (tr.lightmapSize * tr.lightmapSize * 3);
+
+	// check for deluxe mapping
+	if (numLightmaps <= 1)
+	{
+		tr.worldDeluxeMapping = qfalse;
+	}
+	else
+	{
+		tr.worldDeluxeMapping = qtrue;
+		for( i = 0, surf = (dsurface_t *)(fileBase + surfs->fileofs);
+			i < surfs->filelen / sizeof(dsurface_t); i++, surf++ ) {
+			int lightmapNum = LittleLong( surf->lightmapNum );
+
+			if ( lightmapNum >= 0 && (lightmapNum & 1) != 0 ) {
+				tr.worldDeluxeMapping = qfalse;
+				break;
+			}
+		}
+	}
+
+	image = ri.Malloc(tr.lightmapSize * tr.lightmapSize * 4 * 2);
+
+	if (tr.worldDeluxeMapping)
+		numLightmaps >>= 1;
+
+	if(numLightmaps == 1)
+	{
+		//FIXME: HACK: maps with only one lightmap turn up fullbright for some reason.
+		//this avoids this, but isn't the correct solution.
+		numLightmaps++;
+	}
+	else if (r_mergeLightmaps->integer && numLightmaps >= 1024 )
+	{
+		// FIXME: fat light maps don't support more than 1024 light maps
+		ri.Printf(PRINT_WARNING, "WARNING: number of lightmaps > 1024\n");
+		numLightmaps = 1024;
+	}
+
+	// use fat lightmaps of an appropriate size
+	if (r_mergeLightmaps->integer)
+	{
+		tr.fatLightmapSize = 512;
+		tr.fatLightmapStep = tr.fatLightmapSize / tr.lightmapSize;
+
+		// at most MAX_LIGHTMAP_PAGES
+		while (tr.fatLightmapStep * tr.fatLightmapStep * MAX_LIGHTMAP_PAGES < numLightmaps && tr.fatLightmapSize != glConfig.maxTextureSize )
+		{
+			tr.fatLightmapSize <<= 1;
+			tr.fatLightmapStep = tr.fatLightmapSize / tr.lightmapSize;
+		}
+
+		tr.numLightmaps = numLightmaps / (tr.fatLightmapStep * tr.fatLightmapStep);
+
+		if (numLightmaps % (tr.fatLightmapStep * tr.fatLightmapStep) != 0)
+			tr.numLightmaps++;
+	}
+	else
+	{
+		tr.numLightmaps = numLightmaps;
+	}
+
+	tr.lightmaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+
+	if (tr.worldDeluxeMapping)
+	{
+		tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+	}
+
+	if (r_hdr->integer && glRefConfig.textureFloat && glRefConfig.halfFloatPixel)
+		textureInternalFormat = GL_RGBA16F_ARB;
+
+	if (r_mergeLightmaps->integer)
+	{
+		for (i = 0; i < tr.numLightmaps; i++)
+		{
+			tr.lightmaps[i] = R_CreateImage(va("_fatlightmap%d", i), NULL, tr.fatLightmapSize, tr.fatLightmapSize, IMGTYPE_COLORALPHA, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, textureInternalFormat );
+
+			if (tr.worldDeluxeMapping)
+			{
+				tr.deluxemaps[i] = R_CreateImage(va("_fatdeluxemap%d", i), NULL, tr.fatLightmapSize, tr.fatLightmapSize, IMGTYPE_DELUXE, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, 0 );
+			}
+		}
+	}
+
+	for(i = 0; i < numLightmaps; i++)
+	{
+		int xoff = 0, yoff = 0;
+		int lightmapnum = i;
+		// expand the 24 bit on-disk to 32 bit
+
+		if (r_mergeLightmaps->integer)
+		{
+			int lightmaponpage = i % (tr.fatLightmapStep * tr.fatLightmapStep);
+			xoff = (lightmaponpage % tr.fatLightmapStep) * tr.lightmapSize;
+			yoff = (lightmaponpage / tr.fatLightmapStep) * tr.lightmapSize;
+
+			lightmapnum /= (tr.fatLightmapStep * tr.fatLightmapStep);
+		}
+
+		// if (tr.worldLightmapping)
+		{
+			char filename[MAX_QPATH];
+			byte *hdrLightmap = NULL;
+			float lightScale = 1.0f;
+			int size = 0;
+
+			// look for hdr lightmaps
+			if (r_hdr->integer)
+			{
+				Com_sprintf( filename, sizeof( filename ), "maps/%s/lm_%04d.hdr", s_worldData.baseName, i * (tr.worldDeluxeMapping ? 2 : 1) );
+				//ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+				size = ri.FS_ReadFile(filename, (void **)&hdrLightmap);
+			}
+
+			if (hdrLightmap)
+			{
+				byte *p = hdrLightmap;
+				//ri.Printf(PRINT_ALL, "found!\n");
+				
+				/* FIXME: don't just skip over this header and actually parse it */
+				while (size && !(*p == '\n' && *(p+1) == '\n'))
+				{
+					size--;
+					p++;
+				}
+
+				if (!size)
+					ri.Error(ERR_DROP, "Bad header for %s!\n", filename);
+
+				size -= 2;
+				p += 2;
+				
+				while (size && !(*p == '\n'))
+				{
+					size--;
+					p++;
+				}
+
+				size--;
+				p++;
+
+				buf_p = (byte *)p;
+
+#if 0 // HDRFILE_RGBE
+				if (size != tr.lightmapSize * tr.lightmapSize * 4)
+					ri.Error(ERR_DROP, "Bad size for %s (%i)!\n", filename, size);
+#else // HDRFILE_FLOAT
+				if (size != tr.lightmapSize * tr.lightmapSize * 12)
+					ri.Error(ERR_DROP, "Bad size for %s (%i)!\n", filename, size);
+#endif
+			}
+			else
+			{
+				if (tr.worldDeluxeMapping)
+					buf_p = buf + (i * 2) * tr.lightmapSize * tr.lightmapSize * 3;
+				else
+					buf_p = buf + i * tr.lightmapSize * tr.lightmapSize * 3;
+			}
+
+			lightScale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8); //exp2(r_mapOverBrightBits->integer - tr.overbrightBits - 8);
+
+			for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) 
+			{
+				if (r_hdr->integer)
+				{
+					float color[3];
+
+					if (hdrLightmap)
+					{
+#if 0 // HDRFILE_RGBE
+						float exponent = exp2(buf_p[j*4+3] - 128);
+
+						color[0] = buf_p[j*4+0] * exponent;
+						color[1] = buf_p[j*4+1] * exponent;
+						color[2] = buf_p[j*4+2] * exponent;
+#else // HDRFILE_FLOAT
+						memcpy(color, &buf_p[j*12], 12);
+
+						color[0] = LittleFloat(color[0]);
+						color[1] = LittleFloat(color[1]);
+						color[2] = LittleFloat(color[2]);
+#endif
+					}
+					else
+					{
+						//hack: convert LDR lightmap to HDR one
+						color[0] = (buf_p[j*3+0] + 1.0f);
+						color[1] = (buf_p[j*3+1] + 1.0f);
+						color[2] = (buf_p[j*3+2] + 1.0f);
+
+						// if under an arbitrary value (say 12) grey it out
+						// this prevents weird splotches in dimly lit areas
+						if (color[0] + color[1] + color[2] < 12.0f)
+						{
+							float avg = (color[0] + color[1] + color[2]) * 0.3333f;
+							color[0] = avg;
+							color[1] = avg;
+							color[2] = avg;
+						}
+					}
+
+					VectorScale(color, lightScale, color);
+
+					if (glRefConfig.textureFloat && glRefConfig.halfFloatPixel)
+						ColorToRGBA16F(color, (unsigned short *)(&image[j*8]));
+					else
+						ColorToRGBE(color, &image[j*4]);
+				}
+				else
+				{
+					if ( r_lightmap->integer == 2 )
+					{	// color code by intensity as development tool	(FIXME: check range)
+						float r = buf_p[j*3+0];
+						float g = buf_p[j*3+1];
+						float b = buf_p[j*3+2];
+						float intensity;
+						float out[3] = {0.0, 0.0, 0.0};
+
+						intensity = 0.33f * r + 0.685f * g + 0.063f * b;
+
+						if ( intensity > 255 )
+							intensity = 1.0f;
+						else
+							intensity /= 255.0f;
+
+						if ( intensity > maxIntensity )
+							maxIntensity = intensity;
+
+						HSVtoRGB( intensity, 1.00, 0.50, out );
+
+						image[j*4+0] = out[0] * 255;
+						image[j*4+1] = out[1] * 255;
+						image[j*4+2] = out[2] * 255;
+						image[j*4+3] = 255;
+
+						sumIntensity += intensity;
+					}
+					else
+					{
+						R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] );
+						image[j*4+3] = 255;
+					}
+				}
+			}
+
+			if (r_mergeLightmaps->integer)
+				R_UpdateSubImage(tr.lightmaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize);
+			else
+				tr.lightmaps[i] = R_CreateImage(va("*lightmap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_COLORALPHA, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, textureInternalFormat );
+
+			if (hdrLightmap)
+				ri.FS_FreeFile(hdrLightmap);
+		}
+
+		if (tr.worldDeluxeMapping)
+		{
+			buf_p = buf + (i * 2 + 1) * tr.lightmapSize * tr.lightmapSize * 3;
+
+			for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) {
+				image[j*4+0] = buf_p[j*3+0];
+				image[j*4+1] = buf_p[j*3+1];
+				image[j*4+2] = buf_p[j*3+2];
+
+				// make 0,0,0 into 127,127,127
+				if ((image[j*4+0] == 0) && (image[j*4+0] == 0) && (image[j*4+2] == 0))
+				{
+					image[j*4+0] =
+					image[j*4+1] =
+					image[j*4+2] = 127;
+				}
+
+				image[j*4+3] = 255;
+			}
+
+			if (r_mergeLightmaps->integer)
+			{
+				R_UpdateSubImage(tr.deluxemaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize );
+			}
+			else
+			{
+				tr.deluxemaps[i] = R_CreateImage(va("*deluxemap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_DELUXE, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, 0 );
+			}
+		}
+	}
+
+	if ( r_lightmap->integer == 2 )	{
+		ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) );
+	}
+
+	ri.Free(image);
+}
+
+
+static float FatPackU(float input, int lightmapnum)
+{
+	if (lightmapnum < 0)
+		return input;
+
+	if (tr.worldDeluxeMapping)
+		lightmapnum >>= 1;
+
+	lightmapnum %= (tr.fatLightmapStep * tr.fatLightmapStep);
+
+	if(tr.fatLightmapSize > 0)
+	{
+		int             x = lightmapnum % tr.fatLightmapStep;
+
+		return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)x);
+	}
+
+	return input;
+}
+
+static float FatPackV(float input, int lightmapnum)
+{
+	if (lightmapnum < 0)
+		return input;
+
+	if (tr.worldDeluxeMapping)
+		lightmapnum >>= 1;
+
+	lightmapnum %= (tr.fatLightmapStep * tr.fatLightmapStep);
+
+	if(tr.fatLightmapSize > 0)
+	{
+		int             y = lightmapnum / tr.fatLightmapStep;
+
+		return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)y);
+	}
+
+	return input;
+}
+
+
+static int FatLightmap(int lightmapnum)
+{
+	if (lightmapnum < 0)
+		return lightmapnum;
+
+	if (tr.worldDeluxeMapping)
+		lightmapnum >>= 1;
+
+	if (tr.fatLightmapSize > 0)
+	{
+		return lightmapnum / (tr.fatLightmapStep * tr.fatLightmapStep);
+	}
+	
+	return lightmapnum;
+}
+
+/*
+=================
+RE_SetWorldVisData
+
+This is called by the clipmodel subsystem so we can share the 1.8 megs of
+space in big maps...
+=================
+*/
+void		RE_SetWorldVisData( const byte *vis ) {
+	tr.externalVisData = vis;
+}
+
+
+/*
+=================
+R_LoadVisibility
+=================
+*/
+static	void R_LoadVisibility( lump_t *l ) {
+	int		len;
+	byte	*buf;
+
+	len = ( s_worldData.numClusters + 63 ) & ~63;
+	s_worldData.novis = ri.Hunk_Alloc( len, h_low );
+	Com_Memset( s_worldData.novis, 0xff, len );
+
+    len = l->filelen;
+	if ( !len ) {
+		return;
+	}
+	buf = fileBase + l->fileofs;
+
+	s_worldData.numClusters = LittleLong( ((int *)buf)[0] );
+	s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] );
+
+	// CM_Load should have given us the vis data to share, so
+	// we don't need to allocate another copy
+	if ( tr.externalVisData ) {
+		s_worldData.vis = tr.externalVisData;
+	} else {
+		byte	*dest;
+
+		dest = ri.Hunk_Alloc( len - 8, h_low );
+		Com_Memcpy( dest, buf + 8, len - 8 );
+		s_worldData.vis = dest;
+	}
+}
+
+//===============================================================================
+
+
+/*
+===============
+ShaderForShaderNum
+===============
+*/
+static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) {
+	shader_t	*shader;
+	dshader_t	*dsh;
+
+	int _shaderNum = LittleLong( shaderNum );
+	if ( _shaderNum < 0 || _shaderNum >= s_worldData.numShaders ) {
+		ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", _shaderNum );
+	}
+	dsh = &s_worldData.shaders[ _shaderNum ];
+
+	if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+		lightmapNum = LIGHTMAP_BY_VERTEX;
+	}
+
+	if ( r_fullbright->integer ) {
+		lightmapNum = LIGHTMAP_WHITEIMAGE;
+	}
+
+	shader = R_FindShader( dsh->shader, lightmapNum, qtrue );
+
+	// if the shader had errors, just use default shader
+	if ( shader->defaultShader ) {
+		return tr.defaultShader;
+	}
+
+	return shader;
+}
+
+/*
+===============
+ParseFace
+===============
+*/
+static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes  ) {
+	int			i, j;
+	srfSurfaceFace_t	*cv;
+	srfTriangle_t  *tri;
+	int			numVerts, numTriangles, badTriangles;
+	int realLightmapNum;
+
+	realLightmapNum = LittleLong( ds->lightmapNum );
+
+	// get fog volume
+	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+	// get shader value
+	surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
+		surf->shader = tr.defaultShader;
+	}
+
+	numVerts = LittleLong(ds->numVerts);
+	if (numVerts > MAX_FACE_POINTS) {
+		ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numVerts);
+		numVerts = MAX_FACE_POINTS;
+		surf->shader = tr.defaultShader;
+	}
+
+	numTriangles = LittleLong(ds->numIndexes) / 3;
+
+	//cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+	cv = (void *)surf->data;
+	cv->surfaceType = SF_FACE;
+
+	cv->numTriangles = numTriangles;
+	cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low);
+
+	cv->numVerts = numVerts;
+	cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
+
+	// copy vertexes
+	surf->cullinfo.type = CULLINFO_PLANE | CULLINFO_BOX;
+	ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]);
+	verts += LittleLong(ds->firstVert);
+	for(i = 0; i < numVerts; i++)
+	{
+		vec4_t color;
+
+		for(j = 0; j < 3; j++)
+		{
+			cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
+			cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]);
+		}
+		AddPointToBounds(cv->verts[i].xyz, surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]);
+		for(j = 0; j < 2; j++)
+		{
+			cv->verts[i].st[j] = LittleFloat(verts[i].st[j]);
+			//cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
+		}
+		cv->verts[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum);
+		cv->verts[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum);
+
+		if (hdrVertColors)
+		{
+			color[0] = hdrVertColors[(ds->firstVert + i) * 3    ];
+			color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1];
+			color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2];
+		}
+		else
+		{
+			//hack: convert LDR vertex colors to HDR
+			if (r_hdr->integer)
+			{
+				color[0] = verts[i].color[0] + 1.0f;
+				color[1] = verts[i].color[1] + 1.0f;
+				color[2] = verts[i].color[2] + 1.0f;
+			}
+			else
+			{
+				color[0] = verts[i].color[0];
+				color[1] = verts[i].color[1];
+				color[2] = verts[i].color[2];
+			}
+
+		}
+		color[3] = verts[i].color[3] / 255.0f;
+
+		R_ColorShiftLightingFloats( color, cv->verts[i].vertexColors, 1.0f / 255.0f );
+	}
+
+	// copy triangles
+	badTriangles = 0;
+	indexes += LittleLong(ds->firstIndex);
+	for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
+	{
+		for(j = 0; j < 3; j++)
+		{
+			tri->indexes[j] = LittleLong(indexes[i * 3 + j]);
+
+			if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts)
+			{
+				ri.Error(ERR_DROP, "Bad index in face surface");
+			}
+		}
+
+		if ((tri->indexes[0] == tri->indexes[1]) || (tri->indexes[1] == tri->indexes[2]) || (tri->indexes[0] == tri->indexes[2]))
+		{
+			tri--;
+			badTriangles++;
+		}
+	}
+
+	if (badTriangles)
+	{
+		ri.Printf(PRINT_WARNING, "Face has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numTriangles, numVerts, numTriangles - badTriangles);
+		cv->numTriangles -= badTriangles;
+	}
+
+	// take the plane information from the lightmap vector
+	for ( i = 0 ; i < 3 ; i++ ) {
+		cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+	}
+	cv->plane.dist = DotProduct( cv->verts[0].xyz, cv->plane.normal );
+	SetPlaneSignbits( &cv->plane );
+	cv->plane.type = PlaneTypeForNormal( cv->plane.normal );
+	surf->cullinfo.plane = cv->plane;
+
+	surf->data = (surfaceType_t *)cv;
+
+#ifdef USE_VERT_TANGENT_SPACE
+	// Tr3B - calc tangent spaces
+	{
+		srfVert_t      *dv[3];
+
+		for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
+		{
+			dv[0] = &cv->verts[tri->indexes[0]];
+			dv[1] = &cv->verts[tri->indexes[1]];
+			dv[2] = &cv->verts[tri->indexes[2]];
+
+			R_CalcTangentVectors(dv);
+		}
+	}
+#endif
+}
+
+
+/*
+===============
+ParseMesh
+===============
+*/
+static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf ) {
+	srfGridMesh_t	*grid;
+	int				i, j;
+	int				width, height, numPoints;
+	srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE];
+	vec3_t			bounds[2];
+	vec3_t			tmpVec;
+	static surfaceType_t	skipData = SF_SKIP;
+	int realLightmapNum;
+
+	realLightmapNum = LittleLong( ds->lightmapNum );
+
+	// get fog volume
+	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+	// get shader value
+	surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
+		surf->shader = tr.defaultShader;
+	}
+
+	// we may have a nodraw surface, because they might still need to
+	// be around for movement clipping
+	if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) {
+		surf->data = &skipData;
+		return;
+	}
+
+	width = LittleLong( ds->patchWidth );
+	height = LittleLong( ds->patchHeight );
+
+	if(width < 0 || width > MAX_PATCH_SIZE || height < 0 || height > MAX_PATCH_SIZE)
+		ri.Error(ERR_DROP, "ParseMesh: bad size");
+
+	verts += LittleLong( ds->firstVert );
+	numPoints = width * height;
+	for(i = 0; i < numPoints; i++)
+	{
+		vec4_t color;
+
+		for(j = 0; j < 3; j++)
+		{
+			points[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
+			points[i].normal[j] = LittleFloat(verts[i].normal[j]);
+		}
+
+		for(j = 0; j < 2; j++)
+		{
+			points[i].st[j] = LittleFloat(verts[i].st[j]);
+			//points[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
+		}
+		points[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum);
+		points[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum);
+
+		if (hdrVertColors)
+		{
+			color[0] = hdrVertColors[(ds->firstVert + i) * 3    ];
+			color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1];
+			color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2];
+		}
+		else
+		{
+			//hack: convert LDR vertex colors to HDR
+			if (r_hdr->integer)
+			{
+				color[0] = verts[i].color[0] + 1.0f;
+				color[1] = verts[i].color[1] + 1.0f;
+				color[2] = verts[i].color[2] + 1.0f;
+			}
+			else
+			{
+				color[0] = verts[i].color[0];
+				color[1] = verts[i].color[1];
+				color[2] = verts[i].color[2];
+			}
+		}
+		color[3] = verts[i].color[3] / 255.0f;
+
+		R_ColorShiftLightingFloats( color, points[i].vertexColors, 1.0f / 255.0f );
+	}
+
+	// pre-tesseleate
+	grid = R_SubdividePatchToGrid( width, height, points );
+	surf->data = (surfaceType_t *)grid;
+
+	// copy the level of detail origin, which is the center
+	// of the group of all curves that must subdivide the same
+	// to avoid cracking
+	for ( i = 0 ; i < 3 ; i++ ) {
+		bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] );
+		bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] );
+	}
+	VectorAdd( bounds[0], bounds[1], bounds[1] );
+	VectorScale( bounds[1], 0.5f, grid->lodOrigin );
+	VectorSubtract( bounds[0], grid->lodOrigin, tmpVec );
+	grid->lodRadius = VectorLength( tmpVec );
+}
+
+/*
+===============
+ParseTriSurf
+===============
+*/
+static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) {
+	srfTriangles_t *cv;
+	srfTriangle_t  *tri;
+	int             i, j;
+	int             numVerts, numTriangles, badTriangles;
+
+	// get fog volume
+	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+	// get shader
+	surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
+		surf->shader = tr.defaultShader;
+	}
+
+	numVerts = LittleLong(ds->numVerts);
+	numTriangles = LittleLong(ds->numIndexes) / 3;
+
+	//cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+	cv = (void *)surf->data;
+	cv->surfaceType = SF_TRIANGLES;
+
+	cv->numTriangles = numTriangles;
+	cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low);
+
+	cv->numVerts = numVerts;
+	cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low);
+
+	surf->data = (surfaceType_t *) cv;
+
+	// copy vertexes
+	surf->cullinfo.type = CULLINFO_BOX;
+	ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]);
+	verts += LittleLong(ds->firstVert);
+	for(i = 0; i < numVerts; i++)
+	{
+		vec4_t color;
+
+		for(j = 0; j < 3; j++)
+		{
+			cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]);
+			cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]);
+		}
+
+		AddPointToBounds( cv->verts[i].xyz, surf->cullinfo.bounds[0], surf->cullinfo.bounds[1] );
+
+		for(j = 0; j < 2; j++)
+		{
+			cv->verts[i].st[j] = LittleFloat(verts[i].st[j]);
+			cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]);
+		}
+
+		if (hdrVertColors)
+		{
+			color[0] = hdrVertColors[(ds->firstVert + i) * 3    ];
+			color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1];
+			color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2];
+		}
+		else
+		{
+			//hack: convert LDR vertex colors to HDR
+			if (r_hdr->integer)
+			{
+				color[0] = verts[i].color[0] + 1.0f;
+				color[1] = verts[i].color[1] + 1.0f;
+				color[2] = verts[i].color[2] + 1.0f;
+			}
+			else
+			{
+				color[0] = verts[i].color[0];
+				color[1] = verts[i].color[1];
+				color[2] = verts[i].color[2];
+			}
+		}
+		color[3] = verts[i].color[3] / 255.0f;
+
+		R_ColorShiftLightingFloats( color, cv->verts[i].vertexColors, 1.0f / 255.0f );
+	}
+
+	// copy triangles
+	badTriangles = 0;
+	indexes += LittleLong(ds->firstIndex);
+	for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
+	{
+		for(j = 0; j < 3; j++)
+		{
+			tri->indexes[j] = LittleLong(indexes[i * 3 + j]);
+
+			if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts)
+			{
+				ri.Error(ERR_DROP, "Bad index in face surface");
+			}
+		}
+
+		if ((tri->indexes[0] == tri->indexes[1]) || (tri->indexes[1] == tri->indexes[2]) || (tri->indexes[0] == tri->indexes[2]))
+		{
+			tri--;
+			badTriangles++;
+		}
+	}
+
+	if (badTriangles)
+	{
+		ri.Printf(PRINT_WARNING, "Trisurf has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numTriangles, numVerts, numTriangles - badTriangles);
+		cv->numTriangles -= badTriangles;
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	// Tr3B - calc tangent spaces
+	{
+		srfVert_t      *dv[3];
+
+		for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++)
+		{
+			dv[0] = &cv->verts[tri->indexes[0]];
+			dv[1] = &cv->verts[tri->indexes[1]];
+			dv[2] = &cv->verts[tri->indexes[2]];
+
+			R_CalcTangentVectors(dv);
+		}
+	}
+#endif
+}
+
+/*
+===============
+ParseFlare
+===============
+*/
+static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) {
+	srfFlare_t		*flare;
+	int				i;
+
+	// get fog volume
+	surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+	// get shader
+	surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+	if ( r_singleShader->integer && !surf->shader->isSky ) {
+		surf->shader = tr.defaultShader;
+	}
+
+	//flare = ri.Hunk_Alloc( sizeof( *flare ), h_low );
+	flare = (void *)surf->data;
+	flare->surfaceType = SF_FLARE;
+
+	surf->data = (surfaceType_t *)flare;
+
+	for ( i = 0 ; i < 3 ; i++ ) {
+		flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] );
+		flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] );
+		flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+	}
+}
+
+
+/*
+=================
+R_MergedWidthPoints
+
+returns true if there are grid points merged on a width edge
+=================
+*/
+int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) {
+	int i, j;
+
+	for (i = 1; i < grid->width-1; i++) {
+		for (j = i + 1; j < grid->width-1; j++) {
+			if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue;
+			if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue;
+			if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue;
+			return qtrue;
+		}
+	}
+	return qfalse;
+}
+
+/*
+=================
+R_MergedHeightPoints
+
+returns true if there are grid points merged on a height edge
+=================
+*/
+int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) {
+	int i, j;
+
+	for (i = 1; i < grid->height-1; i++) {
+		for (j = i + 1; j < grid->height-1; j++) {
+			if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue;
+			if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue;
+			if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue;
+			return qtrue;
+		}
+	}
+	return qfalse;
+}
+
+/*
+=================
+R_FixSharedVertexLodError_r
+
+NOTE: never sync LoD through grid edges with merged points!
+
+FIXME: write generalized version that also avoids cracks between a patch and one that meets half way?
+=================
+*/
+void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) {
+	int j, k, l, m, n, offset1, offset2, touch;
+	srfGridMesh_t *grid2;
+
+	for ( j = start; j < s_worldData.numsurfaces; j++ ) {
+		//
+		grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
+		// if this surface is not a grid
+		if ( grid2->surfaceType != SF_GRID ) continue;
+		// if the LOD errors are already fixed for this patch
+		if ( grid2->lodFixed == 2 ) continue;
+		// grids in the same LOD group should have the exact same lod radius
+		if ( grid1->lodRadius != grid2->lodRadius ) continue;
+		// grids in the same LOD group should have the exact same lod origin
+		if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+		if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+		if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+		//
+		touch = qfalse;
+		for (n = 0; n < 2; n++) {
+			//
+			if (n) offset1 = (grid1->height-1) * grid1->width;
+			else offset1 = 0;
+			if (R_MergedWidthPoints(grid1, offset1)) continue;
+			for (k = 1; k < grid1->width-1; k++) {
+				for (m = 0; m < 2; m++) {
+
+					if (m) offset2 = (grid2->height-1) * grid2->width;
+					else offset2 = 0;
+					if (R_MergedWidthPoints(grid2, offset2)) continue;
+					for ( l = 1; l < grid2->width-1; l++) {
+					//
+						if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+						if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+						if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+						// ok the points are equal and should have the same lod error
+						grid2->widthLodError[l] = grid1->widthLodError[k];
+						touch = qtrue;
+					}
+				}
+				for (m = 0; m < 2; m++) {
+
+					if (m) offset2 = grid2->width-1;
+					else offset2 = 0;
+					if (R_MergedHeightPoints(grid2, offset2)) continue;
+					for ( l = 1; l < grid2->height-1; l++) {
+					//
+						if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+						if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+						if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+						// ok the points are equal and should have the same lod error
+						grid2->heightLodError[l] = grid1->widthLodError[k];
+						touch = qtrue;
+					}
+				}
+			}
+		}
+		for (n = 0; n < 2; n++) {
+			//
+			if (n) offset1 = grid1->width-1;
+			else offset1 = 0;
+			if (R_MergedHeightPoints(grid1, offset1)) continue;
+			for (k = 1; k < grid1->height-1; k++) {
+				for (m = 0; m < 2; m++) {
+
+					if (m) offset2 = (grid2->height-1) * grid2->width;
+					else offset2 = 0;
+					if (R_MergedWidthPoints(grid2, offset2)) continue;
+					for ( l = 1; l < grid2->width-1; l++) {
+					//
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+						// ok the points are equal and should have the same lod error
+						grid2->widthLodError[l] = grid1->heightLodError[k];
+						touch = qtrue;
+					}
+				}
+				for (m = 0; m < 2; m++) {
+
+					if (m) offset2 = grid2->width-1;
+					else offset2 = 0;
+					if (R_MergedHeightPoints(grid2, offset2)) continue;
+					for ( l = 1; l < grid2->height-1; l++) {
+					//
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+						if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+						// ok the points are equal and should have the same lod error
+						grid2->heightLodError[l] = grid1->heightLodError[k];
+						touch = qtrue;
+					}
+				}
+			}
+		}
+		if (touch) {
+			grid2->lodFixed = 2;
+			R_FixSharedVertexLodError_r ( start, grid2 );
+			//NOTE: this would be correct but makes things really slow
+			//grid2->lodFixed = 1;
+		}
+	}
+}
+
+/*
+=================
+R_FixSharedVertexLodError
+
+This function assumes that all patches in one group are nicely stitched together for the highest LoD.
+If this is not the case this function will still do its job but won't fix the highest LoD cracks.
+=================
+*/
+void R_FixSharedVertexLodError( void ) {
+	int i;
+	srfGridMesh_t *grid1;
+
+	for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+		//
+		grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+		// if this surface is not a grid
+		if ( grid1->surfaceType != SF_GRID )
+			continue;
+		//
+		if ( grid1->lodFixed )
+			continue;
+		//
+		grid1->lodFixed = 2;
+		// recursively fix other patches in the same LOD group
+		R_FixSharedVertexLodError_r( i + 1, grid1);
+	}
+}
+
+
+/*
+===============
+R_StitchPatches
+===============
+*/
+int R_StitchPatches( int grid1num, int grid2num ) {
+	float *v1, *v2;
+	srfGridMesh_t *grid1, *grid2;
+	int k, l, m, n, offset1, offset2, row, column;
+
+	grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
+	grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data;
+	for (n = 0; n < 2; n++) {
+		//
+		if (n) offset1 = (grid1->height-1) * grid1->width;
+		else offset1 = 0;
+		if (R_MergedWidthPoints(grid1, offset1))
+			continue;
+		for (k = 0; k < grid1->width-2; k += 2) {
+
+			for (m = 0; m < 2; m++) {
+
+				if ( grid2->width >= MAX_GRID_SIZE )
+					break;
+				if (m) offset2 = (grid2->height-1) * grid2->width;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->width-1; l++) {
+				//
+					v1 = grid1->verts[k + offset1].xyz;
+					v2 = grid2->verts[l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[k + 2 + offset1].xyz;
+					v2 = grid2->verts[l + 1 + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[l + offset2].xyz;
+					v2 = grid2->verts[l + 1 + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert column into grid2 right after after column l
+					if (m) row = grid2->height-1;
+					else row = 0;
+					grid2 = R_GridInsertColumn( grid2, l+1, row,
+									grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+			for (m = 0; m < 2; m++) {
+
+				if (grid2->height >= MAX_GRID_SIZE)
+					break;
+				if (m) offset2 = grid2->width-1;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->height-1; l++) {
+					//
+					v1 = grid1->verts[k + offset1].xyz;
+					v2 = grid2->verts[grid2->width * l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[k + 2 + offset1].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[grid2->width * l + offset2].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert row into grid2 right after after row l
+					if (m) column = grid2->width-1;
+					else column = 0;
+					grid2 = R_GridInsertRow( grid2, l+1, column,
+										grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+		}
+	}
+	for (n = 0; n < 2; n++) {
+		//
+		if (n) offset1 = grid1->width-1;
+		else offset1 = 0;
+		if (R_MergedHeightPoints(grid1, offset1))
+			continue;
+		for (k = 0; k < grid1->height-2; k += 2) {
+			for (m = 0; m < 2; m++) {
+
+				if ( grid2->width >= MAX_GRID_SIZE )
+					break;
+				if (m) offset2 = (grid2->height-1) * grid2->width;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->width-1; l++) {
+				//
+					v1 = grid1->verts[grid1->width * k + offset1].xyz;
+					v2 = grid2->verts[l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+					v2 = grid2->verts[l + 1 + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[l + offset2].xyz;
+					v2 = grid2->verts[(l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert column into grid2 right after after column l
+					if (m) row = grid2->height-1;
+					else row = 0;
+					grid2 = R_GridInsertColumn( grid2, l+1, row,
+									grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+			for (m = 0; m < 2; m++) {
+
+				if (grid2->height >= MAX_GRID_SIZE)
+					break;
+				if (m) offset2 = grid2->width-1;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->height-1; l++) {
+				//
+					v1 = grid1->verts[grid1->width * k + offset1].xyz;
+					v2 = grid2->verts[grid2->width * l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[grid2->width * l + offset2].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert row into grid2 right after after row l
+					if (m) column = grid2->width-1;
+					else column = 0;
+					grid2 = R_GridInsertRow( grid2, l+1, column,
+									grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+		}
+	}
+	for (n = 0; n < 2; n++) {
+		//
+		if (n) offset1 = (grid1->height-1) * grid1->width;
+		else offset1 = 0;
+		if (R_MergedWidthPoints(grid1, offset1))
+			continue;
+		for (k = grid1->width-1; k > 1; k -= 2) {
+
+			for (m = 0; m < 2; m++) {
+
+				if ( grid2->width >= MAX_GRID_SIZE )
+					break;
+				if (m) offset2 = (grid2->height-1) * grid2->width;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->width-1; l++) {
+				//
+					v1 = grid1->verts[k + offset1].xyz;
+					v2 = grid2->verts[l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[k - 2 + offset1].xyz;
+					v2 = grid2->verts[l + 1 + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[l + offset2].xyz;
+					v2 = grid2->verts[(l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert column into grid2 right after after column l
+					if (m) row = grid2->height-1;
+					else row = 0;
+					grid2 = R_GridInsertColumn( grid2, l+1, row,
+										grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+			for (m = 0; m < 2; m++) {
+
+				if (grid2->height >= MAX_GRID_SIZE)
+					break;
+				if (m) offset2 = grid2->width-1;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->height-1; l++) {
+				//
+					v1 = grid1->verts[k + offset1].xyz;
+					v2 = grid2->verts[grid2->width * l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[k - 2 + offset1].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[grid2->width * l + offset2].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert row into grid2 right after after row l
+					if (m) column = grid2->width-1;
+					else column = 0;
+					grid2 = R_GridInsertRow( grid2, l+1, column,
+										grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+					if (!grid2)
+						break;
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+		}
+	}
+	for (n = 0; n < 2; n++) {
+		//
+		if (n) offset1 = grid1->width-1;
+		else offset1 = 0;
+		if (R_MergedHeightPoints(grid1, offset1))
+			continue;
+		for (k = grid1->height-1; k > 1; k -= 2) {
+			for (m = 0; m < 2; m++) {
+
+				if ( grid2->width >= MAX_GRID_SIZE )
+					break;
+				if (m) offset2 = (grid2->height-1) * grid2->width;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->width-1; l++) {
+				//
+					v1 = grid1->verts[grid1->width * k + offset1].xyz;
+					v2 = grid2->verts[l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+					v2 = grid2->verts[l + 1 + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[l + offset2].xyz;
+					v2 = grid2->verts[(l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert column into grid2 right after after column l
+					if (m) row = grid2->height-1;
+					else row = 0;
+					grid2 = R_GridInsertColumn( grid2, l+1, row,
+										grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+			for (m = 0; m < 2; m++) {
+
+				if (grid2->height >= MAX_GRID_SIZE)
+					break;
+				if (m) offset2 = grid2->width-1;
+				else offset2 = 0;
+				for ( l = 0; l < grid2->height-1; l++) {
+				//
+					v1 = grid1->verts[grid1->width * k + offset1].xyz;
+					v2 = grid2->verts[grid2->width * l + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+
+					v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) > .1)
+						continue;
+					if ( fabs(v1[1] - v2[1]) > .1)
+						continue;
+					if ( fabs(v1[2] - v2[2]) > .1)
+						continue;
+					//
+					v1 = grid2->verts[grid2->width * l + offset2].xyz;
+					v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+					if ( fabs(v1[0] - v2[0]) < .01 &&
+							fabs(v1[1] - v2[1]) < .01 &&
+							fabs(v1[2] - v2[2]) < .01)
+						continue;
+					//
+					//ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+					// insert row into grid2 right after after row l
+					if (m) column = grid2->width-1;
+					else column = 0;
+					grid2 = R_GridInsertRow( grid2, l+1, column,
+										grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+					grid2->lodStitched = qfalse;
+					s_worldData.surfaces[grid2num].data = (void *) grid2;
+					return qtrue;
+				}
+			}
+		}
+	}
+	return qfalse;
+}
+
+/*
+===============
+R_TryStitchPatch
+
+This function will try to stitch patches in the same LoD group together for the highest LoD.
+
+Only single missing vertice cracks will be fixed.
+
+Vertices will be joined at the patch side a crack is first found, at the other side
+of the patch (on the same row or column) the vertices will not be joined and cracks
+might still appear at that side.
+===============
+*/
+int R_TryStitchingPatch( int grid1num ) {
+	int j, numstitches;
+	srfGridMesh_t *grid1, *grid2;
+
+	numstitches = 0;
+	grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
+	for ( j = 0; j < s_worldData.numsurfaces; j++ ) {
+		//
+		grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
+		// if this surface is not a grid
+		if ( grid2->surfaceType != SF_GRID ) continue;
+		// grids in the same LOD group should have the exact same lod radius
+		if ( grid1->lodRadius != grid2->lodRadius ) continue;
+		// grids in the same LOD group should have the exact same lod origin
+		if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+		if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+		if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+		//
+		while (R_StitchPatches(grid1num, j))
+		{
+			numstitches++;
+		}
+	}
+	return numstitches;
+}
+
+/*
+===============
+R_StitchAllPatches
+===============
+*/
+void R_StitchAllPatches( void ) {
+	int i, stitched, numstitches;
+	srfGridMesh_t *grid1;
+
+	numstitches = 0;
+	do
+	{
+		stitched = qfalse;
+		for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+			//
+			grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+			// if this surface is not a grid
+			if ( grid1->surfaceType != SF_GRID )
+				continue;
+			//
+			if ( grid1->lodStitched )
+				continue;
+			//
+			grid1->lodStitched = qtrue;
+			stitched = qtrue;
+			//
+			numstitches += R_TryStitchingPatch( i );
+		}
+	}
+	while (stitched);
+	ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches );
+}
+
+/*
+===============
+R_MovePatchSurfacesToHunk
+===============
+*/
+void R_MovePatchSurfacesToHunk(void) {
+	int i, size;
+	srfGridMesh_t *grid, *hunkgrid;
+
+	for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+		//
+		grid = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+		// if this surface is not a grid
+		if ( grid->surfaceType != SF_GRID )
+			continue;
+		//
+		size = sizeof(*grid);
+		hunkgrid = ri.Hunk_Alloc(size, h_low);
+		Com_Memcpy(hunkgrid, grid, size);
+
+		hunkgrid->widthLodError = ri.Hunk_Alloc( grid->width * 4, h_low );
+		Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 );
+
+		hunkgrid->heightLodError = ri.Hunk_Alloc( grid->height * 4, h_low );
+		Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 );
+
+		hunkgrid->numTriangles = grid->numTriangles;
+		hunkgrid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low);
+		Com_Memcpy(hunkgrid->triangles, grid->triangles, grid->numTriangles * sizeof(srfTriangle_t));
+
+		hunkgrid->numVerts = grid->numVerts;
+		hunkgrid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low);
+		Com_Memcpy(hunkgrid->verts, grid->verts, grid->numVerts * sizeof(srfVert_t));
+
+		R_FreeSurfaceGridMesh( grid );
+
+		s_worldData.surfaces[i].data = (void *) hunkgrid;
+	}
+}
+
+
+/*
+=================
+BSPSurfaceCompare
+compare function for qsort()
+=================
+*/
+static int BSPSurfaceCompare(const void *a, const void *b)
+{
+	msurface_t   *aa, *bb;
+
+	aa = *(msurface_t **) a;
+	bb = *(msurface_t **) b;
+
+	// shader first
+	if(aa->shader->sortedIndex < bb->shader->sortedIndex)
+		return -1;
+
+	else if(aa->shader->sortedIndex > bb->shader->sortedIndex)
+		return 1;
+
+	// by fogIndex
+	if(aa->fogIndex < bb->fogIndex)
+		return -1;
+
+	else if(aa->fogIndex > bb->fogIndex)
+		return 1;
+
+	return 0;
+}
+
+
+static void CopyVert(const srfVert_t * in, srfVert_t * out)
+{
+	int             j;
+
+	for(j = 0; j < 3; j++)
+	{
+		out->xyz[j]       = in->xyz[j];
+#ifdef USE_VERT_TANGENT_SPACE
+		out->tangent[j]   = in->tangent[j];
+		out->bitangent[j] = in->bitangent[j];
+#endif
+		out->normal[j]    = in->normal[j];
+		out->lightdir[j]  = in->lightdir[j];
+	}
+
+	for(j = 0; j < 2; j++)
+	{
+		out->st[j] = in->st[j];
+		out->lightmap[j] = in->lightmap[j];
+	}
+
+	for(j = 0; j < 4; j++)
+	{
+		out->vertexColors[j] = in->vertexColors[j];
+	}
+}
+
+
+/*
+===============
+R_CreateWorldVBO
+===============
+*/
+static void R_CreateWorldVBO(void)
+{
+	int             i, j, k;
+
+	int             numVerts;
+	srfVert_t      *verts;
+
+	int             numTriangles;
+	srfTriangle_t  *triangles;
+
+    int             numSurfaces;
+	msurface_t   *surface;
+	msurface_t  **surfacesSorted;
+
+	int             startTime, endTime;
+
+	startTime = ri.Milliseconds();
+
+	numVerts = 0;
+	numTriangles = 0;
+	numSurfaces = 0;
+	for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++)
+	{
+		if(*surface->data == SF_FACE)
+		{
+			srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface->data;
+
+			if(face->numVerts)
+				numVerts += face->numVerts;
+
+			if(face->numTriangles)
+				numTriangles += face->numTriangles;
+
+			numSurfaces++;
+		}
+		else if(*surface->data == SF_GRID)
+		{
+			srfGridMesh_t  *grid = (srfGridMesh_t *) surface->data;
+
+			if(grid->numVerts)
+				numVerts += grid->numVerts;
+
+			if(grid->numTriangles)
+				numTriangles += grid->numTriangles;
+
+			numSurfaces++;
+		}
+		else if(*surface->data == SF_TRIANGLES)
+		{
+			srfTriangles_t *tri = (srfTriangles_t *) surface->data;
+
+			if(tri->numVerts)
+				numVerts += tri->numVerts;
+
+			if(tri->numTriangles)
+				numTriangles += tri->numTriangles;
+
+			numSurfaces++;
+		}
+	}
+
+	if(!numVerts || !numTriangles)
+		return;
+
+	ri.Printf(PRINT_ALL, "...calculating world VBO ( %i verts %i tris )\n", numVerts, numTriangles);
+
+	// create arrays
+
+	verts = ri.Hunk_AllocateTempMemory(numVerts * sizeof(srfVert_t));
+
+	triangles = ri.Hunk_AllocateTempMemory(numTriangles * sizeof(srfTriangle_t));
+
+	// presort surfaces
+	surfacesSorted = ri.Malloc(numSurfaces * sizeof(*surfacesSorted));
+
+	j = 0;
+	for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces; k++, surface++)
+	{
+		if(*surface->data == SF_FACE || *surface->data == SF_GRID || *surface->data == SF_TRIANGLES)
+		{
+			surfacesSorted[j++] = surface;
+		}
+	}
+
+	qsort(surfacesSorted, numSurfaces, sizeof(*surfacesSorted), BSPSurfaceCompare);
+
+	// set up triangle indices
+	numVerts = 0;
+	numTriangles = 0;
+	for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k])
+	{
+		if(*surface->data == SF_FACE)
+		{
+			srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
+
+			srf->firstIndex = numTriangles * 3;
+
+			if(srf->numTriangles)
+			{
+				srfTriangle_t  *tri;
+
+				for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
+				{
+					for(j = 0; j < 3; j++)
+					{
+						triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
+					}
+				}
+
+				numTriangles += srf->numTriangles;
+			}
+
+			if(srf->numVerts)
+				numVerts += srf->numVerts;
+		}
+		else if(*surface->data == SF_GRID)
+		{
+			srfGridMesh_t  *srf = (srfGridMesh_t *) surface->data;
+
+			srf->firstIndex = numTriangles * 3;
+
+			if(srf->numTriangles)
+			{
+				srfTriangle_t  *tri;
+
+				for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
+				{
+					for(j = 0; j < 3; j++)
+					{
+						triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
+					}
+				}
+
+				numTriangles += srf->numTriangles;
+			}
+
+			if(srf->numVerts)
+				numVerts += srf->numVerts;
+		}
+		else if(*surface->data == SF_TRIANGLES)
+		{
+			srfTriangles_t *srf = (srfTriangles_t *) surface->data;
+
+			srf->firstIndex = numTriangles * 3;
+
+			if(srf->numTriangles)
+			{
+				srfTriangle_t  *tri;
+
+				for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++)
+				{
+					for(j = 0; j < 3; j++)
+					{
+						triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j];
+					}
+				}
+
+				numTriangles += srf->numTriangles;
+			}
+
+			if(srf->numVerts)
+				numVerts += srf->numVerts;
+		}
+	}
+
+	// build vertices
+	numVerts = 0;
+	for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k])
+	{
+		if(*surface->data == SF_FACE)
+		{
+			srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
+
+			srf->firstVert = numVerts;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					CopyVert(&srf->verts[i], &verts[numVerts + i]);
+				}
+
+				numVerts += srf->numVerts;
+			}
+		}
+		else if(*surface->data == SF_GRID)
+		{
+			srfGridMesh_t  *srf = (srfGridMesh_t *) surface->data;
+
+			srf->firstVert = numVerts;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					CopyVert(&srf->verts[i], &verts[numVerts + i]);
+				}
+
+				numVerts += srf->numVerts;
+			}
+		}
+		else if(*surface->data == SF_TRIANGLES)
+		{
+			srfTriangles_t *srf = (srfTriangles_t *) surface->data;
+
+			srf->firstVert = numVerts;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					CopyVert(&srf->verts[i], &verts[numVerts + i]);
+				}
+
+				numVerts += srf->numVerts;
+			}
+		}
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	s_worldData.vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", 0), numVerts, verts,
+								   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BITANGENT |
+								   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
+#else
+	s_worldData.vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", 0), numVerts, verts,
+								   ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD |
+								   ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC);
+#endif
+
+	s_worldData.ibo = R_CreateIBO2(va("staticBspModel0_IBO %i", 0), numTriangles, triangles, VBO_USAGE_STATIC);
+
+	endTime = ri.Milliseconds();
+	ri.Printf(PRINT_ALL, "world VBO calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0);
+
+	// point triangle surfaces to world VBO
+	for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k])
+	{
+		if(*surface->data == SF_FACE)
+		{
+			srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
+
+			if( srf->numVerts && srf->numTriangles)
+			{
+				srf->vbo = s_worldData.vbo;
+				srf->ibo = s_worldData.ibo;
+			}
+		}
+		else if(*surface->data == SF_GRID)
+		{
+			srfGridMesh_t  *srf = (srfGridMesh_t *) surface->data;
+
+			if( srf->numVerts && srf->numTriangles)
+			{
+				srf->vbo = s_worldData.vbo;
+				srf->ibo = s_worldData.ibo;
+			}
+		}
+		else if(*surface->data == SF_TRIANGLES)
+		{
+			srfTriangles_t *srf = (srfTriangles_t *) surface->data;
+
+			if( srf->numVerts && srf->numTriangles)
+			{
+				srf->vbo = s_worldData.vbo;
+				srf->ibo = s_worldData.ibo;
+			}
+		}
+	}
+
+
+	startTime = ri.Milliseconds();
+
+	ri.Free(surfacesSorted);
+
+	ri.Hunk_FreeTempMemory(triangles);
+	ri.Hunk_FreeTempMemory(verts);
+}
+
+/*
+===============
+R_LoadSurfaces
+===============
+*/
+static	void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) {
+	dsurface_t	*in;
+	msurface_t	*out;
+	drawVert_t	*dv;
+	int			*indexes;
+	int			count;
+	int			numFaces, numMeshes, numTriSurfs, numFlares;
+	int			i;
+	float *hdrVertColors = NULL;
+
+	numFaces = 0;
+	numMeshes = 0;
+	numTriSurfs = 0;
+	numFlares = 0;
+
+	in = (void *)(fileBase + surfs->fileofs);
+	if (surfs->filelen % sizeof(*in))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	count = surfs->filelen / sizeof(*in);
+
+	dv = (void *)(fileBase + verts->fileofs);
+	if (verts->filelen % sizeof(*dv))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+	indexes = (void *)(fileBase + indexLump->fileofs);
+	if ( indexLump->filelen % sizeof(*indexes))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+	out = ri.Hunk_Alloc ( count * sizeof(*out), h_low );	
+
+	s_worldData.surfaces = out;
+	s_worldData.numsurfaces = count;
+	s_worldData.surfacesViewCount = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesViewCount), h_low );
+	s_worldData.surfacesDlightBits = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesDlightBits), h_low );
+	s_worldData.surfacesPshadowBits = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesPshadowBits), h_low );
+
+	// load hdr vertex colors
+	if (r_hdr->integer)
+	{
+		char filename[MAX_QPATH];
+		int size;
+
+		Com_sprintf( filename, sizeof( filename ), "maps/%s/vertlight.raw", s_worldData.baseName);
+		//ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+		size = ri.FS_ReadFile(filename, (void **)&hdrVertColors);
+
+		if (hdrVertColors)
+		{
+			//ri.Printf(PRINT_ALL, "Found!\n");
+			if (size != sizeof(float) * 3 * (verts->filelen / sizeof(*dv)))
+				ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!\n", filename, size, (int)(sizeof(float)) * 3 * (verts->filelen / sizeof(*dv)));
+		}
+	}
+
+
+	// Two passes, allocate surfaces first, then load them full of data
+	// This ensures surfaces are close together to reduce L2 cache misses when using VBOs,
+	// which don't actually use the verts and tris
+	in = (void *)(fileBase + surfs->fileofs);
+	out = s_worldData.surfaces;
+	for ( i = 0 ; i < count ; i++, in++, out++ ) {
+		switch ( LittleLong( in->surfaceType ) ) {
+			case MST_PATCH:
+				// FIXME: do this
+				break;
+			case MST_TRIANGLE_SOUP:
+				out->data = ri.Hunk_Alloc( sizeof(srfTriangles_t), h_low);
+				break;
+			case MST_PLANAR:
+				out->data = ri.Hunk_Alloc( sizeof(srfSurfaceFace_t), h_low);
+				break;
+			case MST_FLARE:
+				out->data = ri.Hunk_Alloc( sizeof(srfFlare_t), h_low);
+				break;
+			default:
+				break;
+		}
+	}
+
+	in = (void *)(fileBase + surfs->fileofs);
+	out = s_worldData.surfaces;
+	for ( i = 0 ; i < count ; i++, in++, out++ ) {
+		switch ( LittleLong( in->surfaceType ) ) {
+		case MST_PATCH:
+			ParseMesh ( in, dv, hdrVertColors, out );
+			{
+				srfGridMesh_t *surface = (srfGridMesh_t *)out->data;
+
+				out->cullinfo.type = CULLINFO_BOX | CULLINFO_SPHERE;
+				VectorCopy(surface->meshBounds[0], out->cullinfo.bounds[0]);
+				VectorCopy(surface->meshBounds[1], out->cullinfo.bounds[1]);
+				VectorCopy(surface->localOrigin, out->cullinfo.localOrigin);
+				out->cullinfo.radius = surface->meshRadius;
+			}
+			numMeshes++;
+			break;
+		case MST_TRIANGLE_SOUP:
+			ParseTriSurf( in, dv, hdrVertColors, out, indexes );
+			numTriSurfs++;
+			break;
+		case MST_PLANAR:
+			ParseFace( in, dv, hdrVertColors, out, indexes );
+			numFaces++;
+			break;
+		case MST_FLARE:
+			ParseFlare( in, dv, out, indexes );
+			{
+				out->cullinfo.type = CULLINFO_NONE;
+			}
+			numFlares++;
+			break;
+		default:
+			ri.Error( ERR_DROP, "Bad surfaceType" );
+		}
+	}
+
+	if (hdrVertColors)
+	{
+		ri.FS_FreeFile(hdrVertColors);
+	}
+
+#ifdef PATCH_STITCHING
+	R_StitchAllPatches();
+#endif
+
+	R_FixSharedVertexLodError();
+
+#ifdef PATCH_STITCHING
+	R_MovePatchSurfacesToHunk();
+#endif
+
+	ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", 
+		numFaces, numMeshes, numTriSurfs, numFlares );
+}
+
+
+
+/*
+=================
+R_LoadSubmodels
+=================
+*/
+static	void R_LoadSubmodels( lump_t *l ) {
+	dmodel_t	*in;
+	bmodel_t	*out;
+	int			i, j, count;
+
+	in = (void *)(fileBase + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	count = l->filelen / sizeof(*in);
+
+	s_worldData.numBModels = count;
+	s_worldData.bmodels = out = ri.Hunk_Alloc( count * sizeof(*out), h_low );
+
+	for ( i=0 ; i<count ; i++, in++, out++ ) {
+		model_t *model;
+
+		model = R_AllocModel();
+
+		assert( model != NULL );			// this should never happen
+		if ( model == NULL ) {
+			ri.Error(ERR_DROP, "R_LoadSubmodels: R_AllocModel() failed");
+		}
+
+		model->type = MOD_BRUSH;
+		model->bmodel = out;
+		Com_sprintf( model->name, sizeof( model->name ), "*%d", i );
+
+		for (j=0 ; j<3 ; j++) {
+			out->bounds[0][j] = LittleFloat (in->mins[j]);
+			out->bounds[1][j] = LittleFloat (in->maxs[j]);
+		}
+
+		out->firstSurface = LittleLong( in->firstSurface );
+		out->numSurfaces = LittleLong( in->numSurfaces );
+
+		if(i == 0)
+		{
+			// Tr3B: add this for limiting VBO surface creation
+			s_worldData.numWorldSurfaces = out->numSurfaces;
+		}
+	}
+}
+
+
+
+//==================================================================
+
+/*
+=================
+R_SetParent
+=================
+*/
+static	void R_SetParent (mnode_t *node, mnode_t *parent)
+{
+	node->parent = parent;
+	if (node->contents != -1)
+		return;
+	R_SetParent (node->children[0], node);
+	R_SetParent (node->children[1], node);
+}
+
+/*
+=================
+R_LoadNodesAndLeafs
+=================
+*/
+static	void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) {
+	int			i, j, p;
+	dnode_t		*in;
+	dleaf_t		*inLeaf;
+	mnode_t 	*out;
+	int			numNodes, numLeafs;
+
+	in = (void *)(fileBase + nodeLump->fileofs);
+	if (nodeLump->filelen % sizeof(dnode_t) ||
+		leafLump->filelen % sizeof(dleaf_t) ) {
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	}
+	numNodes = nodeLump->filelen / sizeof(dnode_t);
+	numLeafs = leafLump->filelen / sizeof(dleaf_t);
+
+	out = ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low);	
+
+	s_worldData.nodes = out;
+	s_worldData.numnodes = numNodes + numLeafs;
+	s_worldData.numDecisionNodes = numNodes;
+
+	// load nodes
+	for ( i=0 ; i<numNodes; i++, in++, out++)
+	{
+		for (j=0 ; j<3 ; j++)
+		{
+			out->mins[j] = LittleLong (in->mins[j]);
+			out->maxs[j] = LittleLong (in->maxs[j]);
+		}
+	
+		p = LittleLong(in->planeNum);
+		out->plane = s_worldData.planes + p;
+
+		out->contents = CONTENTS_NODE;	// differentiate from leafs
+
+		for (j=0 ; j<2 ; j++)
+		{
+			p = LittleLong (in->children[j]);
+			if (p >= 0)
+				out->children[j] = s_worldData.nodes + p;
+			else
+				out->children[j] = s_worldData.nodes + numNodes + (-1 - p);
+		}
+	}
+	
+	// load leafs
+	inLeaf = (void *)(fileBase + leafLump->fileofs);
+	for ( i=0 ; i<numLeafs ; i++, inLeaf++, out++)
+	{
+		for (j=0 ; j<3 ; j++)
+		{
+			out->mins[j] = LittleLong (inLeaf->mins[j]);
+			out->maxs[j] = LittleLong (inLeaf->maxs[j]);
+		}
+
+		out->cluster = LittleLong(inLeaf->cluster);
+		out->area = LittleLong(inLeaf->area);
+
+		if ( out->cluster >= s_worldData.numClusters ) {
+			s_worldData.numClusters = out->cluster + 1;
+		}
+
+		out->firstmarksurface = LittleLong(inLeaf->firstLeafSurface);
+		out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces);
+	}	
+
+	// chain decendants
+	R_SetParent (s_worldData.nodes, NULL);
+}
+
+//=============================================================================
+
+/*
+=================
+R_LoadShaders
+=================
+*/
+static	void R_LoadShaders( lump_t *l ) {	
+	int		i, count;
+	dshader_t	*in, *out;
+	
+	in = (void *)(fileBase + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	count = l->filelen / sizeof(*in);
+	out = ri.Hunk_Alloc ( count*sizeof(*out), h_low );
+
+	s_worldData.shaders = out;
+	s_worldData.numShaders = count;
+
+	Com_Memcpy( out, in, count*sizeof(*out) );
+
+	for ( i=0 ; i<count ; i++ ) {
+		out[i].surfaceFlags = LittleLong( out[i].surfaceFlags );
+		out[i].contentFlags = LittleLong( out[i].contentFlags );
+	}
+}
+
+
+/*
+=================
+R_LoadMarksurfaces
+=================
+*/
+static	void R_LoadMarksurfaces (lump_t *l)
+{	
+	int		i, j, count;
+	int		*in;
+	int     *out;
+	
+	in = (void *)(fileBase + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	count = l->filelen / sizeof(*in);
+	out = ri.Hunk_Alloc ( count*sizeof(*out), h_low);	
+
+	s_worldData.marksurfaces = out;
+	s_worldData.nummarksurfaces = count;
+
+	for ( i=0 ; i<count ; i++)
+	{
+		j = LittleLong(in[i]);
+		out[i] = j;
+	}
+}
+
+
+/*
+=================
+R_LoadPlanes
+=================
+*/
+static	void R_LoadPlanes( lump_t *l ) {
+	int			i, j;
+	cplane_t	*out;
+	dplane_t 	*in;
+	int			count;
+	int			bits;
+	
+	in = (void *)(fileBase + l->fileofs);
+	if (l->filelen % sizeof(*in))
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	count = l->filelen / sizeof(*in);
+	out = ri.Hunk_Alloc ( count*2*sizeof(*out), h_low);	
+	
+	s_worldData.planes = out;
+	s_worldData.numplanes = count;
+
+	for ( i=0 ; i<count ; i++, in++, out++) {
+		bits = 0;
+		for (j=0 ; j<3 ; j++) {
+			out->normal[j] = LittleFloat (in->normal[j]);
+			if (out->normal[j] < 0) {
+				bits |= 1<<j;
+			}
+		}
+
+		out->dist = LittleFloat (in->dist);
+		out->type = PlaneTypeForNormal( out->normal );
+		out->signbits = bits;
+	}
+}
+
+/*
+=================
+R_LoadFogs
+
+=================
+*/
+static	void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) {
+	int			i;
+	fog_t		*out;
+	dfog_t		*fogs;
+	dbrush_t 	*brushes, *brush;
+	dbrushside_t	*sides;
+	int			count, brushesCount, sidesCount;
+	int			sideNum;
+	int			planeNum;
+	shader_t	*shader;
+	float		d;
+	int			firstSide;
+
+	fogs = (void *)(fileBase + l->fileofs);
+	if (l->filelen % sizeof(*fogs)) {
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	}
+	count = l->filelen / sizeof(*fogs);
+
+	// create fog strucutres for them
+	s_worldData.numfogs = count + 1;
+	s_worldData.fogs = ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low);
+	out = s_worldData.fogs + 1;
+
+	if ( !count ) {
+		return;
+	}
+
+	brushes = (void *)(fileBase + brushesLump->fileofs);
+	if (brushesLump->filelen % sizeof(*brushes)) {
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	}
+	brushesCount = brushesLump->filelen / sizeof(*brushes);
+
+	sides = (void *)(fileBase + sidesLump->fileofs);
+	if (sidesLump->filelen % sizeof(*sides)) {
+		ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+	}
+	sidesCount = sidesLump->filelen / sizeof(*sides);
+
+	for ( i=0 ; i<count ; i++, fogs++) {
+		out->originalBrushNumber = LittleLong( fogs->brushNum );
+
+		if ( (unsigned)out->originalBrushNumber >= brushesCount ) {
+			ri.Error( ERR_DROP, "fog brushNumber out of range" );
+		}
+		brush = brushes + out->originalBrushNumber;
+
+		firstSide = LittleLong( brush->firstSide );
+
+			if ( (unsigned)firstSide > sidesCount - 6 ) {
+			ri.Error( ERR_DROP, "fog brush sideNumber out of range" );
+		}
+
+		// brushes are always sorted with the axial sides first
+		sideNum = firstSide + 0;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist;
+
+		sideNum = firstSide + 1;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[1][0] = s_worldData.planes[ planeNum ].dist;
+
+		sideNum = firstSide + 2;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist;
+
+		sideNum = firstSide + 3;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[1][1] = s_worldData.planes[ planeNum ].dist;
+
+		sideNum = firstSide + 4;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist;
+
+		sideNum = firstSide + 5;
+		planeNum = LittleLong( sides[ sideNum ].planeNum );
+		out->bounds[1][2] = s_worldData.planes[ planeNum ].dist;
+
+		// get information from the shader for fog parameters
+		shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue );
+
+		out->parms = shader->fogParms;
+
+		out->colorInt = ColorBytes4 ( shader->fogParms.color[0] * tr.identityLight, 
+			                          shader->fogParms.color[1] * tr.identityLight, 
+			                          shader->fogParms.color[2] * tr.identityLight, 1.0 );
+
+		d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque;
+		out->tcScale = 1.0f / ( d * 8 );
+
+		// set the gradient vector
+		sideNum = LittleLong( fogs->visibleSide );
+
+		if ( sideNum == -1 ) {
+			out->hasSurface = qfalse;
+		} else {
+			out->hasSurface = qtrue;
+			planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum );
+			VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface );
+			out->surface[3] = -s_worldData.planes[ planeNum ].dist;
+		}
+
+		out++;
+	}
+
+}
+
+
+/*
+================
+R_LoadLightGrid
+
+================
+*/
+void R_LoadLightGrid( lump_t *l ) {
+	int		i;
+	vec3_t	maxs;
+	int		numGridPoints;
+	world_t	*w;
+	float	*wMins, *wMaxs;
+
+	w = &s_worldData;
+
+	w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0];
+	w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1];
+	w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2];
+
+	wMins = w->bmodels[0].bounds[0];
+	wMaxs = w->bmodels[0].bounds[1];
+
+	for ( i = 0 ; i < 3 ; i++ ) {
+		w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] );
+		maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] );
+		w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1;
+	}
+
+	numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
+
+	if ( l->filelen != numGridPoints * 8 ) {
+		ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" );
+		w->lightGridData = NULL;
+		return;
+	}
+
+	w->lightGridData = ri.Hunk_Alloc( l->filelen, h_low );
+	Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen );
+
+	// deal with overbright bits
+	for ( i = 0 ; i < numGridPoints ; i++ ) {
+		R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] );
+		R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] );
+	}
+
+	// load hdr lightgrid
+	if (r_hdr->integer)
+	{
+		char filename[MAX_QPATH];
+		float *hdrLightGrid;
+		int size;
+
+		Com_sprintf( filename, sizeof( filename ), "maps/%s/lightgrid.raw", s_worldData.baseName);
+		//ri.Printf(PRINT_ALL, "looking for %s\n", filename);
+
+		size = ri.FS_ReadFile(filename, (void **)&hdrLightGrid);
+
+		if (hdrLightGrid)
+		{
+			float lightScale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits);
+
+			//ri.Printf(PRINT_ALL, "found!\n");
+
+			if (size != sizeof(float) * 6 * numGridPoints)
+			{
+				ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!\n", filename, size, (int)(sizeof(float)) * 6 * numGridPoints);
+			}
+
+			w->hdrLightGrid = ri.Hunk_Alloc(size, h_low);
+
+			for (i = 0; i < numGridPoints ; i++)
+			{
+				w->hdrLightGrid[i * 6    ] = hdrLightGrid[i * 6    ] * lightScale;
+				w->hdrLightGrid[i * 6 + 1] = hdrLightGrid[i * 6 + 1] * lightScale;
+				w->hdrLightGrid[i * 6 + 2] = hdrLightGrid[i * 6 + 2] * lightScale;
+				w->hdrLightGrid[i * 6 + 3] = hdrLightGrid[i * 6 + 3] * lightScale;
+				w->hdrLightGrid[i * 6 + 4] = hdrLightGrid[i * 6 + 4] * lightScale;
+				w->hdrLightGrid[i * 6 + 5] = hdrLightGrid[i * 6 + 5] * lightScale;
+			}
+		}
+
+		if (hdrLightGrid)
+			ri.FS_FreeFile(hdrLightGrid);
+	}
+}
+
+/*
+================
+R_LoadEntities
+================
+*/
+void R_LoadEntities( lump_t *l ) {
+	char *p, *token, *s;
+	char keyname[MAX_TOKEN_CHARS];
+	char value[MAX_TOKEN_CHARS];
+	world_t	*w;
+
+	w = &s_worldData;
+	w->lightGridSize[0] = 64;
+	w->lightGridSize[1] = 64;
+	w->lightGridSize[2] = 128;
+
+	p = (char *)(fileBase + l->fileofs);
+
+	// store for reference by the cgame
+	w->entityString = ri.Hunk_Alloc( l->filelen + 1, h_low );
+	strcpy( w->entityString, p );
+	w->entityParsePoint = w->entityString;
+
+	token = COM_ParseExt( &p, qtrue );
+	if (!*token || *token != '{') {
+		return;
+	}
+
+	// only parse the world spawn
+	while ( 1 ) {	
+		// parse key
+		token = COM_ParseExt( &p, qtrue );
+
+		if ( !*token || *token == '}' ) {
+			break;
+		}
+		Q_strncpyz(keyname, token, sizeof(keyname));
+
+		// parse value
+		token = COM_ParseExt( &p, qtrue );
+
+		if ( !*token || *token == '}' ) {
+			break;
+		}
+		Q_strncpyz(value, token, sizeof(value));
+
+		// check for remapping of shaders for vertex lighting
+		s = "vertexremapshader";
+		if (!Q_strncmp(keyname, s, strlen(s)) ) {
+			s = strchr(value, ';');
+			if (!s) {
+				ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value );
+				break;
+			}
+			*s++ = 0;
+			if (r_vertexLight->integer) {
+				R_RemapShader(value, s, "0");
+			}
+			continue;
+		}
+		// check for remapping of shaders
+		s = "remapshader";
+		if (!Q_strncmp(keyname, s, strlen(s)) ) {
+			s = strchr(value, ';');
+			if (!s) {
+				ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value );
+				break;
+			}
+			*s++ = 0;
+			R_RemapShader(value, s, "0");
+			continue;
+		}
+		// check for a different grid size
+		if (!Q_stricmp(keyname, "gridsize")) {
+			sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] );
+			continue;
+		}
+
+		// check for auto exposure
+		if (!Q_stricmp(keyname, "autoExposureMinMax")) {
+			sscanf(value, "%f %f", &tr.autoExposureMinMax[0], &tr.autoExposureMinMax[1]);
+			continue;
+		}
+	}
+}
+
+/*
+=================
+R_GetEntityToken
+=================
+*/
+qboolean R_GetEntityToken( char *buffer, int size ) {
+	const char	*s;
+
+	s = COM_Parse( &s_worldData.entityParsePoint );
+	Q_strncpyz( buffer, s, size );
+	if ( !s_worldData.entityParsePoint || !s[0] ) {
+		s_worldData.entityParsePoint = s_worldData.entityString;
+		return qfalse;
+	} else {
+		return qtrue;
+	}
+}
+
+
+/*
+=================
+R_MergeLeafSurfaces
+
+Merges surfaces that share a common leaf
+=================
+*/
+void R_MergeLeafSurfaces(void)
+{
+	int i, j, k;
+	int numWorldSurfaces;
+	int mergedSurfIndex;
+	int numMergedSurfaces;
+	int numUnmergedSurfaces;
+	IBO_t *ibo;
+
+	msurface_t *mergedSurf;
+
+	glIndex_t *iboIndexes, *outIboIndexes;
+	int numIboIndexes;
+
+	int startTime, endTime;
+
+	startTime = ri.Milliseconds();
+
+	numWorldSurfaces = s_worldData.numWorldSurfaces;
+
+	// use viewcount to keep track of mergers
+	for (i = 0; i < numWorldSurfaces; i++)
+	{
+		s_worldData.surfacesViewCount[i] = -1;
+	}
+
+	// create ibo
+	ibo = tr.ibos[tr.numIBOs++] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
+	memset(ibo, 0, sizeof(*ibo));
+	Q_strncpyz(ibo->name, "staticWorldMesh_IBO_mergedSurfs", sizeof(ibo->name));
+
+	// allocate more than we need
+	iboIndexes = outIboIndexes = ri.Malloc(s_worldData.ibo->indexesSize);
+
+	// mark matching surfaces
+	for (i = 0; i < s_worldData.numnodes - s_worldData.numDecisionNodes; i++)
+	{
+		mnode_t *leaf = s_worldData.nodes + s_worldData.numDecisionNodes + i;
+
+		for (j = 0; j < leaf->nummarksurfaces; j++)
+		{
+			msurface_t *surf1;
+			shader_t *shader1;
+			int fogIndex1;
+			int surfNum1;
+
+			surfNum1 = *(s_worldData.marksurfaces + leaf->firstmarksurface + j);
+
+			if (s_worldData.surfacesViewCount[surfNum1] != -1)
+				continue;
+
+			surf1 = s_worldData.surfaces + surfNum1;
+
+			if ((*surf1->data != SF_GRID) && (*surf1->data != SF_TRIANGLES) && (*surf1->data != SF_FACE))
+				continue;
+
+			shader1 = surf1->shader;
+
+			if(shader1->isSky)
+				continue;
+
+			if(shader1->isPortal)
+				continue;
+
+			if(ShaderRequiresCPUDeforms(shader1))
+				continue;
+
+			fogIndex1 = surf1->fogIndex;
+
+			s_worldData.surfacesViewCount[surfNum1] = surfNum1;
+
+			for (k = j + 1; k < leaf->nummarksurfaces; k++)
+			{
+				msurface_t *surf2;
+				shader_t *shader2;
+				int fogIndex2;
+				int surfNum2;
+
+				surfNum2 = *(s_worldData.marksurfaces + leaf->firstmarksurface + k);
+
+				if (s_worldData.surfacesViewCount[surfNum2] != -1)
+					continue;
+				
+				surf2 = s_worldData.surfaces + surfNum2;
+
+				if ((*surf2->data != SF_GRID) && (*surf2->data != SF_TRIANGLES) && (*surf2->data != SF_FACE))
+					continue;
+
+				shader2 = surf2->shader;
+
+				if (shader1 != shader2)
+					continue;
+
+				fogIndex2 = surf2->fogIndex;
+
+				if (fogIndex1 != fogIndex2)
+					continue;
+
+				s_worldData.surfacesViewCount[surfNum2] = surfNum1;
+			}
+		}
+	}
+
+	// count merged/unmerged surfaces
+	numMergedSurfaces = 0;
+	numUnmergedSurfaces = 0;
+	for (i = 0; i < numWorldSurfaces; i++)
+	{
+		if (s_worldData.surfacesViewCount[i] == i)
+		{
+			numMergedSurfaces++;
+		}
+		else if (s_worldData.surfacesViewCount[i] == -1)
+		{
+			numUnmergedSurfaces++;
+		}
+	}
+
+	// Allocate merged surfaces
+	s_worldData.mergedSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfaces) * numMergedSurfaces, h_low);
+	s_worldData.mergedSurfacesViewCount = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesViewCount) * numMergedSurfaces, h_low);
+	s_worldData.mergedSurfacesDlightBits = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesDlightBits) * numMergedSurfaces, h_low);
+	s_worldData.numMergedSurfaces = numMergedSurfaces;
+	
+	// view surfaces are like mark surfaces, except negative ones represent merged surfaces
+	// -1 represents 0, -2 represents 1, and so on
+	s_worldData.viewSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.viewSurfaces) * s_worldData.nummarksurfaces, h_low);
+
+	// copy view surfaces into mark surfaces
+	for (i = 0; i < s_worldData.nummarksurfaces; i++)
+	{
+		s_worldData.viewSurfaces[i] = s_worldData.marksurfaces[i];
+	}
+
+	// actually merge surfaces
+	numIboIndexes = 0;
+	mergedSurfIndex = 0;
+	mergedSurf = s_worldData.mergedSurfaces;
+	for (i = 0; i < numWorldSurfaces; i++)
+	{
+		msurface_t *surf1;
+
+		vec3_t bounds[2];
+
+		int numSurfsToMerge;
+		int numTriangles;
+		int numVerts;
+		int firstIndex;
+
+		srfVBOMesh_t *vboSurf;
+
+		if (s_worldData.surfacesViewCount[i] != i)
+			continue;
+
+		surf1 = s_worldData.surfaces + i;
+
+		// count verts, indexes, and surfaces
+		numSurfsToMerge = 0;
+		numTriangles = 0;
+		numVerts = 0;
+		for (j = i; j < numWorldSurfaces; j++)
+		{
+			msurface_t *surf2;
+
+			if (s_worldData.surfacesViewCount[j] != i)
+				continue;
+
+			surf2 = s_worldData.surfaces + j;
+
+			switch(*surf2->data)
+			{
+				case SF_FACE:
+					{
+						srfSurfaceFace_t *face;
+
+						face = (srfSurfaceFace_t *) surf2->data;
+						numTriangles += face->numTriangles;
+						numVerts += face->numVerts;
+					}
+					break;
+
+				case SF_GRID:
+					{
+						srfGridMesh_t *grid;
+
+						grid = (srfGridMesh_t *) surf2->data;
+						numTriangles += grid->numTriangles;
+						numVerts += grid->numVerts;
+					}
+					break;
+
+				case SF_TRIANGLES:
+					{
+						srfTriangles_t *tris;
+
+						tris = (srfTriangles_t *) surf2->data;
+						numTriangles += tris->numTriangles;
+						numVerts += tris->numVerts;
+					}
+					break;
+
+				default:
+					break;
+			}
+
+			numSurfsToMerge++;
+		}
+
+		if (numVerts == 0 || numTriangles == 0 || numSurfsToMerge < 2)
+		{
+			continue;
+		}
+
+		// Merge surfaces (indexes) and calculate bounds
+		ClearBounds(bounds[0], bounds[1]);
+		firstIndex = numIboIndexes;
+		for (j = i; j < numWorldSurfaces; j++)
+		{
+			msurface_t *surf2;
+
+			if (s_worldData.surfacesViewCount[j] != i)
+				continue;
+
+			surf2 = s_worldData.surfaces + j;
+
+			AddPointToBounds(surf2->cullinfo.bounds[0], bounds[0], bounds[1]);
+			AddPointToBounds(surf2->cullinfo.bounds[1], bounds[0], bounds[1]);
+
+			switch(*surf2->data)
+			{
+				case SF_FACE:
+					{
+						srfSurfaceFace_t *face;
+
+						face = (srfSurfaceFace_t *) surf2->data;
+
+						for (k = 0; k < face->numTriangles; k++)
+						{
+							*outIboIndexes++ = face->triangles[k].indexes[0] + face->firstVert;
+							*outIboIndexes++ = face->triangles[k].indexes[1] + face->firstVert;
+							*outIboIndexes++ = face->triangles[k].indexes[2] + face->firstVert;
+							numIboIndexes += 3;
+						}
+					}
+					break;
+
+				case SF_GRID:
+					{
+						srfGridMesh_t *grid;
+
+						grid = (srfGridMesh_t *) surf2->data;
+
+						for (k = 0; k < grid->numTriangles; k++)
+						{
+							*outIboIndexes++ = grid->triangles[k].indexes[0] + grid->firstVert;
+							*outIboIndexes++ = grid->triangles[k].indexes[1] + grid->firstVert;
+							*outIboIndexes++ = grid->triangles[k].indexes[2] + grid->firstVert;
+							numIboIndexes += 3;
+						}
+					}
+					break;
+
+				case SF_TRIANGLES:
+					{
+						srfTriangles_t *tris;
+
+						tris = (srfTriangles_t *) surf2->data;
+
+						for (k = 0; k < tris->numTriangles; k++)
+						{
+							*outIboIndexes++ = tris->triangles[k].indexes[0] + tris->firstVert;
+							*outIboIndexes++ = tris->triangles[k].indexes[1] + tris->firstVert;
+							*outIboIndexes++ = tris->triangles[k].indexes[2] + tris->firstVert;
+							numIboIndexes += 3;
+						}
+					}
+					break;
+
+				// never happens, but silences a compile warning
+				default:
+					break;
+			}
+		}
+
+		vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
+		memset(vboSurf, 0, sizeof(*vboSurf));
+		vboSurf->surfaceType = SF_VBO_MESH;
+
+		vboSurf->vbo = s_worldData.vbo;
+		vboSurf->ibo = ibo;
+
+		vboSurf->numIndexes = numTriangles * 3;
+		vboSurf->numVerts = numVerts;
+		vboSurf->firstIndex = firstIndex;
+
+		vboSurf->shader = surf1->shader;
+		vboSurf->fogIndex = surf1->fogIndex;
+
+		VectorCopy(bounds[0], vboSurf->bounds[0]);
+		VectorCopy(bounds[1], vboSurf->bounds[1]);
+
+		VectorCopy(bounds[0], mergedSurf->cullinfo.bounds[0]);
+		VectorCopy(bounds[1], mergedSurf->cullinfo.bounds[1]);
+
+		mergedSurf->cullinfo.type = CULLINFO_BOX;
+		mergedSurf->data          = (surfaceType_t *)vboSurf;
+		mergedSurf->fogIndex      = surf1->fogIndex;
+		mergedSurf->shader        = surf1->shader;
+
+		// redirect view surfaces to this surf
+		for (j = i; j < numWorldSurfaces; j++)
+		{
+			if (s_worldData.surfacesViewCount[j] != i)
+				continue;
+
+			for (k = 0; k < s_worldData.nummarksurfaces; k++)
+			{
+				int *mark = s_worldData.marksurfaces + k;
+				int *view = s_worldData.viewSurfaces + k;
+
+				if (*mark == j)
+					*view = -(mergedSurfIndex + 1);
+			}
+		}
+
+		mergedSurfIndex++;
+		mergedSurf++;
+	}
+
+	// finish up the ibo
+	R_SyncRenderThread();
+
+	qglGenBuffersARB(1, &ibo->indexesVBO);
+
+	R_BindIBO(ibo);
+
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, numIboIndexes * sizeof(*iboIndexes), iboIndexes, GL_STATIC_DRAW_ARB);
+
+	R_BindNullIBO();
+
+	GL_CheckErrors();
+
+	ri.Free(iboIndexes);
+
+	endTime = ri.Milliseconds();
+
+	ri.Printf(PRINT_ALL, "Processed %d surfaces into %d merged, %d unmerged in %5.2f seconds\n", 
+		numWorldSurfaces, numMergedSurfaces, numUnmergedSurfaces, (endTime - startTime) / 1000.0f);
+
+	// reset viewcounts
+	for (i = 0; i < numWorldSurfaces; i++)
+	{
+		s_worldData.surfacesViewCount[i] = -1;
+	}
+}
+
+
+void R_CalcVertexLightDirs( void )
+{
+	int i, k;
+	msurface_t *surface;
+
+	for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++)
+	{
+		if(*surface->data == SF_FACE)
+		{
+			srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData );
+				}
+			}
+		}
+		else if(*surface->data == SF_GRID)
+		{
+			srfGridMesh_t  *srf = (srfGridMesh_t *) surface->data;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData );
+				}
+			}
+		}
+		else if(*surface->data == SF_TRIANGLES)
+		{
+			srfTriangles_t *srf = (srfTriangles_t *) surface->data;
+
+			if(srf->numVerts)
+			{
+				for(i = 0; i < srf->numVerts; i++)
+				{
+					R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData );
+				}
+			}
+		}
+	}
+}
+
+
+/*
+=================
+RE_LoadWorldMap
+
+Called directly from cgame
+=================
+*/
+void RE_LoadWorldMap( const char *name ) {
+	int			i;
+	dheader_t	*header;
+	union {
+		byte *b;
+		void *v;
+	} buffer;
+	byte		*startMarker;
+
+	if ( tr.worldMapLoaded ) {
+		ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" );
+	}
+
+	// set default map light scale
+	tr.mapLightScale = 1.0f;
+
+	// set default sun direction to be used if it isn't
+	// overridden by a shader
+	tr.sunDirection[0] = 0.45f;
+	tr.sunDirection[1] = 0.3f;
+	tr.sunDirection[2] = 0.9f;
+
+	VectorNormalize( tr.sunDirection );
+
+	// set default autoexposure settings
+	tr.autoExposureMinMax[0] = -2.0f;
+	tr.autoExposureMinMax[1] = 2.0f;
+
+	// set default tone mapping settings
+	tr.toneMinAvgMaxLevel[0] = -3.25f;
+	tr.toneMinAvgMaxLevel[1] = -1.0f;
+	tr.toneMinAvgMaxLevel[2] = 1.0f;
+
+	tr.worldMapLoaded = qtrue;
+
+	// load it
+    ri.FS_ReadFile( name, &buffer.v );
+	if ( !buffer.b ) {
+		ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name);
+	}
+
+	// clear tr.world so if the level fails to load, the next
+	// try will not look at the partially loaded version
+	tr.world = NULL;
+
+	Com_Memset( &s_worldData, 0, sizeof( s_worldData ) );
+	Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );
+
+	Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) );
+	COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName));
+
+	startMarker = ri.Hunk_Alloc(0, h_low);
+	c_gridVerts = 0;
+
+	header = (dheader_t *)buffer.b;
+	fileBase = (byte *)header;
+
+	i = LittleLong (header->version);
+	if ( i != BSP_VERSION ) {
+		ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", 
+			name, i, BSP_VERSION);
+	}
+
+	// swap all the lumps
+	for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
+		((int *)header)[i] = LittleLong ( ((int *)header)[i]);
+	}
+
+	// load into heap
+	R_LoadEntities( &header->lumps[LUMP_ENTITIES] );
+	R_LoadShaders( &header->lumps[LUMP_SHADERS] );
+	R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], &header->lumps[LUMP_SURFACES] );
+	R_LoadPlanes (&header->lumps[LUMP_PLANES]);
+	R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] );
+	R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] );
+	R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]);
+	R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
+	R_LoadSubmodels (&header->lumps[LUMP_MODELS]);
+	R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] );
+	R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] );
+
+	// determine vertex light directions
+	R_CalcVertexLightDirs();
+
+	// create static VBOS from the world
+	R_CreateWorldVBO();
+	if (r_mergeLeafSurfaces->integer)
+	{
+		R_MergeLeafSurfaces();
+	}
+
+	s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker;
+
+	// only set tr.world now that we know the entire level has loaded properly
+	tr.world = &s_worldData;
+
+	// make sure the VBO glState entries are safe
+	R_BindNullVBO();
+	R_BindNullIBO();
+
+    ri.FS_FreeFile( buffer.v );
+}

Added: trunk/code/rend2/tr_cmds.c
===================================================================
--- trunk/code/rend2/tr_cmds.c	                        (rev 0)
+++ trunk/code/rend2/tr_cmds.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,666 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+volatile renderCommandList_t	*renderCommandList;
+
+volatile qboolean	renderThreadActive;
+
+
+/*
+=====================
+R_PerformanceCounters
+=====================
+*/
+void R_PerformanceCounters( void ) {
+	if ( !r_speeds->integer ) {
+		// clear the counters even if we aren't printing
+		Com_Memset( &tr.pc, 0, sizeof( tr.pc ) );
+		Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) );
+		return;
+	}
+
+	if (r_speeds->integer == 1) {
+		ri.Printf (PRINT_ALL, "%i/%i/%i shaders/batches/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n",
+			backEnd.pc.c_shaders, backEnd.pc.c_surfBatches, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, 
+			backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, 
+			R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); 
+	} else if (r_speeds->integer == 2) {
+		ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip  %i sout %i bin %i bclip %i bout\n",
+			tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, 
+			tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out );
+		ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip  %i sout %i bin %i bclip %i bout\n",
+			tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, 
+			tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out );
+	} else if (r_speeds->integer == 3) {
+		ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster );
+	} else if (r_speeds->integer == 4) {
+		if ( backEnd.pc.c_dlightVertexes ) {
+			ri.Printf (PRINT_ALL, "dlight srf:%i  culled:%i  verts:%i  tris:%i\n", 
+				tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled,
+				backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 );
+		}
+	} 
+	else if (r_speeds->integer == 5 )
+	{
+		ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar );
+	}
+	else if (r_speeds->integer == 6 )
+	{
+		ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", 
+			backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders );
+	}
+	else if (r_speeds->integer == 7 )
+	{
+		ri.Printf( PRINT_ALL, "VBO draws: static %i dynamic %i\nMultidraws: %i merged %i\n",
+			backEnd.pc.c_staticVboDraws, backEnd.pc.c_dynamicVboDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged );
+		ri.Printf( PRINT_ALL, "GLSL binds: %i  draws: gen %i light %i fog %i dlight %i\n",
+			backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws);
+	}
+
+	Com_Memset( &tr.pc, 0, sizeof( tr.pc ) );
+	Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) );
+}
+
+
+/*
+====================
+R_InitCommandBuffers
+====================
+*/
+void R_InitCommandBuffers( void ) {
+	glConfig.smpActive = qfalse;
+	if ( r_smp->integer ) {
+		ri.Printf( PRINT_ALL, "Trying SMP acceleration...\n" );
+		if ( GLimp_SpawnRenderThread( RB_RenderThread ) ) {
+			ri.Printf( PRINT_ALL, "...succeeded.\n" );
+			glConfig.smpActive = qtrue;
+		} else {
+			ri.Printf( PRINT_ALL, "...failed.\n" );
+		}
+	}
+}
+
+/*
+====================
+R_ShutdownCommandBuffers
+====================
+*/
+void R_ShutdownCommandBuffers( void ) {
+	// kill the rendering thread
+	if ( glConfig.smpActive ) {
+		GLimp_WakeRenderer( NULL );
+		glConfig.smpActive = qfalse;
+	}
+}
+
+/*
+====================
+R_IssueRenderCommands
+====================
+*/
+int	c_blockedOnRender;
+int	c_blockedOnMain;
+
+void R_IssueRenderCommands( qboolean runPerformanceCounters ) {
+	renderCommandList_t	*cmdList;
+
+	cmdList = &backEndData[tr.smpFrame]->commands;
+	assert(cmdList);
+	// add an end-of-list command
+	*(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST;
+
+	// clear it out, in case this is a sync and not a buffer flip
+	cmdList->used = 0;
+
+	if ( glConfig.smpActive ) {
+		// if the render thread is not idle, wait for it
+		if ( renderThreadActive ) {
+			c_blockedOnRender++;
+			if ( r_showSmp->integer ) {
+				ri.Printf( PRINT_ALL, "R" );
+			}
+		} else {
+			c_blockedOnMain++;
+			if ( r_showSmp->integer ) {
+				ri.Printf( PRINT_ALL, "." );
+			}
+		}
+
+		// sleep until the renderer has completed
+		GLimp_FrontEndSleep();
+	}
+
+	// at this point, the back end thread is idle, so it is ok
+	// to look at its performance counters
+	if ( runPerformanceCounters ) {
+		R_PerformanceCounters();
+	}
+
+	// actually start the commands going
+	if ( !r_skipBackEnd->integer ) {
+		// let it start on the new batch
+		if ( !glConfig.smpActive ) {
+			RB_ExecuteRenderCommands( cmdList->cmds );
+		} else {
+			GLimp_WakeRenderer( cmdList );
+		}
+	}
+}
+
+
+/*
+====================
+R_SyncRenderThread
+
+Issue any pending commands and wait for them to complete.
+After exiting, the render thread will have completed its work
+and will remain idle and the main thread is free to issue
+OpenGL calls until R_IssueRenderCommands is called.
+====================
+*/
+void R_SyncRenderThread( void ) {
+	if ( !tr.registered ) {
+		return;
+	}
+	R_IssueRenderCommands( qfalse );
+
+	if ( !glConfig.smpActive ) {
+		return;
+	}
+	GLimp_FrontEndSleep();
+}
+
+/*
+============
+R_GetCommandBuffer
+
+make sure there is enough command space, waiting on the
+render thread if needed.
+============
+*/
+void *R_GetCommandBuffer( int bytes ) {
+	renderCommandList_t	*cmdList;
+
+	cmdList = &backEndData[tr.smpFrame]->commands;
+	bytes = PAD(bytes, sizeof(void *));
+
+	// always leave room for the end of list command
+	if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) {
+		if ( bytes > MAX_RENDER_COMMANDS - 4 ) {
+			ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes );
+		}
+		// if we run out of room, just start dropping commands
+		return NULL;
+	}
+
+	cmdList->used += bytes;
+
+	return cmdList->cmds + cmdList->used - bytes;
+}
+
+
+/*
+=============
+R_AddDrawSurfCmd
+
+=============
+*/
+void	R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+	drawSurfsCommand_t	*cmd;
+
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_DRAW_SURFS;
+
+	cmd->drawSurfs = drawSurfs;
+	cmd->numDrawSurfs = numDrawSurfs;
+
+	cmd->refdef = tr.refdef;
+	cmd->viewParms = tr.viewParms;
+}
+
+
+/*
+=============
+R_AddCapShadowmapCmd
+
+=============
+*/
+void	R_AddCapShadowmapCmd( int map, int cubeSide ) {
+	capShadowmapCommand_t	*cmd;
+
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_CAPSHADOWMAP;
+
+	cmd->map = map;
+	cmd->cubeSide = cubeSide;
+}
+
+
+/*
+=============
+R_PostProcessingCmd
+
+=============
+*/
+void	R_AddPostProcessCmd( ) {
+	postProcessCommand_t	*cmd;
+
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_POSTPROCESS;
+
+	cmd->refdef = tr.refdef;
+	cmd->viewParms = tr.viewParms;
+}
+
+/*
+=============
+RE_SetColor
+
+Passing NULL will set the color to white
+=============
+*/
+void	RE_SetColor( const float *rgba ) {
+	setColorCommand_t	*cmd;
+
+  if ( !tr.registered ) {
+    return;
+  }
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_SET_COLOR;
+	if ( !rgba ) {
+		static float colorWhite[4] = { 1, 1, 1, 1 };
+
+		rgba = colorWhite;
+	}
+
+	cmd->color[0] = rgba[0];
+	cmd->color[1] = rgba[1];
+	cmd->color[2] = rgba[2];
+	cmd->color[3] = rgba[3];
+}
+
+
+/*
+=============
+RE_StretchPic
+=============
+*/
+void RE_StretchPic ( float x, float y, float w, float h, 
+					  float s1, float t1, float s2, float t2, qhandle_t hShader ) {
+	stretchPicCommand_t	*cmd;
+
+  if (!tr.registered) {
+    return;
+  }
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_STRETCH_PIC;
+	cmd->shader = R_GetShaderByHandle( hShader );
+	cmd->x = x;
+	cmd->y = y;
+	cmd->w = w;
+	cmd->h = h;
+	cmd->s1 = s1;
+	cmd->t1 = t1;
+	cmd->s2 = s2;
+	cmd->t2 = t2;
+}
+
+#define MODE_RED_CYAN	1
+#define MODE_RED_BLUE	2
+#define MODE_RED_GREEN	3
+#define MODE_GREEN_MAGENTA 4
+#define MODE_MAX	MODE_GREEN_MAGENTA
+
+void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode)
+{
+	rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE;
+	
+	if(colormode > MODE_MAX)
+	{
+		if(stereoFrame == STEREO_LEFT)
+			stereoFrame = STEREO_RIGHT;
+		else if(stereoFrame == STEREO_RIGHT)
+			stereoFrame = STEREO_LEFT;
+		
+		colormode -= MODE_MAX;
+	}
+	
+	if(colormode == MODE_GREEN_MAGENTA)
+	{
+		if(stereoFrame == STEREO_LEFT)
+			rgba[0] = rgba[2] = GL_FALSE;
+		else if(stereoFrame == STEREO_RIGHT)
+			rgba[1] = GL_FALSE;
+	}
+	else
+	{
+		if(stereoFrame == STEREO_LEFT)
+			rgba[1] = rgba[2] = GL_FALSE;
+		else if(stereoFrame == STEREO_RIGHT)
+		{
+			rgba[0] = GL_FALSE;
+		
+			if(colormode == MODE_RED_BLUE)
+				rgba[1] = GL_FALSE;
+			else if(colormode == MODE_RED_GREEN)
+				rgba[2] = GL_FALSE;
+		}
+	}
+}
+
+
+/*
+====================
+RE_BeginFrame
+
+If running in stereo, RE_BeginFrame will be called twice
+for each RE_EndFrame
+====================
+*/
+void RE_BeginFrame( stereoFrame_t stereoFrame ) {
+	drawBufferCommand_t	*cmd = NULL;
+	colorMaskCommand_t *colcmd = NULL;
+
+	if ( !tr.registered ) {
+		return;
+	}
+	glState.finishCalled = qfalse;
+
+	tr.frameCount++;
+	tr.frameSceneNum = 0;
+
+	//
+	// do overdraw measurement
+	//
+	if ( r_measureOverdraw->integer )
+	{
+		if ( glConfig.stencilBits < 4 )
+		{
+			ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits );
+			ri.Cvar_Set( "r_measureOverdraw", "0" );
+			r_measureOverdraw->modified = qfalse;
+		}
+		else if ( r_shadows->integer == 2 )
+		{
+			ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" );
+			ri.Cvar_Set( "r_measureOverdraw", "0" );
+			r_measureOverdraw->modified = qfalse;
+		}
+		else
+		{
+			R_SyncRenderThread();
+			qglEnable( GL_STENCIL_TEST );
+			qglStencilMask( ~0U );
+			qglClearStencil( 0U );
+			qglStencilFunc( GL_ALWAYS, 0U, ~0U );
+			qglStencilOp( GL_KEEP, GL_INCR, GL_INCR );
+		}
+		r_measureOverdraw->modified = qfalse;
+	}
+	else
+	{
+		// this is only reached if it was on and is now off
+		if ( r_measureOverdraw->modified ) {
+			R_SyncRenderThread();
+			qglDisable( GL_STENCIL_TEST );
+		}
+		r_measureOverdraw->modified = qfalse;
+	}
+
+	//
+	// texturemode stuff
+	//
+	if ( r_textureMode->modified ) {
+		R_SyncRenderThread();
+		GL_TextureMode( r_textureMode->string );
+		r_textureMode->modified = qfalse;
+	}
+
+	//
+	// gamma stuff
+	//
+	if ( r_gamma->modified ) {
+		r_gamma->modified = qfalse;
+
+		R_SyncRenderThread();
+		R_SetColorMappings();
+	}
+
+	// check for errors
+	if ( !r_ignoreGLErrors->integer )
+	{
+		int	err;
+
+		R_SyncRenderThread();
+		if ((err = qglGetError()) != GL_NO_ERROR)
+			ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err);
+	}
+
+	if (glConfig.stereoEnabled) {
+		if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+			return;
+			
+		cmd->commandId = RC_DRAW_BUFFER;
+		
+		if ( stereoFrame == STEREO_LEFT ) {
+			cmd->buffer = (int)GL_BACK_LEFT;
+		} else if ( stereoFrame == STEREO_RIGHT ) {
+			cmd->buffer = (int)GL_BACK_RIGHT;
+		} else {
+			ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame );
+		}
+	}
+	else
+	{
+		if(r_anaglyphMode->integer)
+		{
+			if(r_anaglyphMode->modified)
+			{
+				// clear both, front and backbuffer.
+				qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+				backEnd.colorMask[0] = GL_FALSE;
+				backEnd.colorMask[1] = GL_FALSE;
+				backEnd.colorMask[2] = GL_FALSE;
+				backEnd.colorMask[3] = GL_FALSE;
+				qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+				
+				qglDrawBuffer(GL_FRONT);
+				qglClear(GL_COLOR_BUFFER_BIT);
+				qglDrawBuffer(GL_BACK);
+				qglClear(GL_COLOR_BUFFER_BIT);
+				
+				if (glRefConfig.framebufferObject)
+				{
+					// clear all framebuffers
+					// FIXME: must be a better way to do this
+					int i;
+
+					for (i = 0; i < 3; i++)
+					{
+						if (i == 1 && !tr.msaaResolveFbo)
+							continue;
+
+						switch(i)
+						{
+							case 0:
+								FBO_Bind(tr.renderFbo);
+								break;
+
+							case 1:
+								FBO_Bind(tr.msaaResolveFbo);
+								break;
+
+							case 2:
+								FBO_Bind(tr.screenScratchFbo);
+								break;
+						}
+
+						qglDrawBuffer(GL_FRONT);
+						qglClear(GL_COLOR_BUFFER_BIT);
+						qglDrawBuffer(GL_BACK);
+						qglClear(GL_COLOR_BUFFER_BIT);	
+					}
+
+					FBO_Bind(NULL);
+				}
+
+				r_anaglyphMode->modified = qfalse;
+			}
+			
+			if(stereoFrame == STEREO_LEFT)
+			{
+				if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+					return;
+				
+				if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) )
+					return;
+			}
+			else if(stereoFrame == STEREO_RIGHT)
+			{
+				clearDepthCommand_t *cldcmd;
+				
+				if( !(cldcmd = R_GetCommandBuffer(sizeof(*cldcmd))) )
+					return;
+
+				cldcmd->commandId = RC_CLEARDEPTH;
+
+				if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) )
+					return;
+			}
+			else
+				ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame );
+
+			R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer);
+			colcmd->commandId = RC_COLORMASK;
+		}
+		else
+		{
+			if(stereoFrame != STEREO_CENTER)
+				ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame );
+
+			if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+				return;
+		}
+
+		if(cmd)
+		{
+			cmd->commandId = RC_DRAW_BUFFER;
+
+			if(r_anaglyphMode->modified)
+			{
+				qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+				backEnd.colorMask[0] = 0;
+				backEnd.colorMask[1] = 0;
+				backEnd.colorMask[2] = 0;
+				backEnd.colorMask[3] = 0;
+				r_anaglyphMode->modified = qfalse;
+			}
+
+			if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT"))
+				cmd->buffer = (int)GL_FRONT;
+			else
+				cmd->buffer = (int)GL_BACK;
+		}
+	}
+	
+	tr.refdef.stereoFrame = stereoFrame;
+}
+
+
+/*
+=============
+RE_EndFrame
+
+Returns the number of msec spent in the back end
+=============
+*/
+void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) {
+	swapBuffersCommand_t	*cmd;
+
+	if ( !tr.registered ) {
+		return;
+	}
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_SWAP_BUFFERS;
+
+	R_IssueRenderCommands( qtrue );
+
+	// use the other buffers next frame, because another CPU
+	// may still be rendering into the current ones
+	R_ToggleSmpFrame();
+
+	if ( frontEndMsec ) {
+		*frontEndMsec = tr.frontEndMsec;
+	}
+	tr.frontEndMsec = 0;
+	if ( backEndMsec ) {
+		*backEndMsec = backEnd.pc.msec;
+	}
+	backEnd.pc.msec = 0;
+}
+
+/*
+=============
+RE_TakeVideoFrame
+=============
+*/
+void RE_TakeVideoFrame( int width, int height,
+		byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg )
+{
+	videoFrameCommand_t	*cmd;
+
+	if( !tr.registered ) {
+		return;
+	}
+
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if( !cmd ) {
+		return;
+	}
+
+	cmd->commandId = RC_VIDEOFRAME;
+
+	cmd->width = width;
+	cmd->height = height;
+	cmd->captureBuffer = captureBuffer;
+	cmd->encodeBuffer = encodeBuffer;
+	cmd->motionJpeg = motionJpeg;
+}

Added: trunk/code/rend2/tr_curve.c
===================================================================
--- trunk/code/rend2/tr_curve.c	                        (rev 0)
+++ trunk/code/rend2/tr_curve.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,806 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+
+This file does all of the processing necessary to turn a raw grid of points
+read from the map file into a srfGridMesh_t ready for rendering.
+
+The level of detail solution is direction independent, based only on subdivided
+distance from the true curve.
+
+Only a single entry point:
+
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+								srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) {
+
+*/
+
+
+/*
+============
+LerpDrawVert
+============
+*/
+static void LerpDrawVert( srfVert_t *a, srfVert_t *b, srfVert_t *out ) {
+	out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]);
+	out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]);
+	out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]);
+
+	out->st[0] = 0.5f * (a->st[0] + b->st[0]);
+	out->st[1] = 0.5f * (a->st[1] + b->st[1]);
+
+	out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]);
+	out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]);
+
+	out->vertexColors[0] = 0.5f * (a->vertexColors[0] + b->vertexColors[0]);
+	out->vertexColors[1] = 0.5f * (a->vertexColors[1] + b->vertexColors[1]);
+	out->vertexColors[2] = 0.5f * (a->vertexColors[2] + b->vertexColors[2]);
+	out->vertexColors[3] = 0.5f * (a->vertexColors[3] + b->vertexColors[3]);
+}
+
+/*
+============
+Transpose
+============
+*/
+static void Transpose( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+	int		i, j;
+	srfVert_t	temp;
+
+	if ( width > height ) {
+		for ( i = 0 ; i < height ; i++ ) {
+			for ( j = i + 1 ; j < width ; j++ ) {
+				if ( j < height ) {
+					// swap the value
+					temp = ctrl[j][i];
+					ctrl[j][i] = ctrl[i][j];
+					ctrl[i][j] = temp;
+				} else {
+					// just copy
+					ctrl[j][i] = ctrl[i][j];
+				}
+			}
+		}
+	} else {
+		for ( i = 0 ; i < width ; i++ ) {
+			for ( j = i + 1 ; j < height ; j++ ) {
+				if ( j < width ) {
+					// swap the value
+					temp = ctrl[i][j];
+					ctrl[i][j] = ctrl[j][i];
+					ctrl[j][i] = temp;
+				} else {
+					// just copy
+					ctrl[i][j] = ctrl[j][i];
+				}
+			}
+		}
+	}
+
+}
+
+
+/*
+=================
+MakeMeshNormals
+
+Handles all the complicated wrapping and degenerate cases
+=================
+*/
+static void MakeMeshNormals( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+	int		i, j, k, dist;
+	vec3_t	normal;
+	vec3_t	sum;
+	int		count = 0;
+	vec3_t	base;
+	vec3_t	delta;
+	int		x, y;
+	srfVert_t	*dv;
+	vec3_t		around[8], temp;
+	qboolean	good[8];
+	qboolean	wrapWidth, wrapHeight;
+	float		len;
+static	int	neighbors[8][2] = {
+	{0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1}
+	};
+
+	wrapWidth = qfalse;
+	for ( i = 0 ; i < height ; i++ ) {
+		VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta );
+		len = VectorLengthSquared( delta );
+		if ( len > 1.0 ) {
+			break;
+		}
+	}
+	if ( i == height ) {
+		wrapWidth = qtrue;
+	}
+
+	wrapHeight = qfalse;
+	for ( i = 0 ; i < width ; i++ ) {
+		VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta );
+		len = VectorLengthSquared( delta );
+		if ( len > 1.0 ) {
+			break;
+		}
+	}
+	if ( i == width) {
+		wrapHeight = qtrue;
+	}
+
+
+	for ( i = 0 ; i < width ; i++ ) {
+		for ( j = 0 ; j < height ; j++ ) {
+			count = 0;
+			dv = &ctrl[j][i];
+			VectorCopy( dv->xyz, base );
+			for ( k = 0 ; k < 8 ; k++ ) {
+				VectorClear( around[k] );
+				good[k] = qfalse;
+
+				for ( dist = 1 ; dist <= 3 ; dist++ ) {
+					x = i + neighbors[k][0] * dist;
+					y = j + neighbors[k][1] * dist;
+					if ( wrapWidth ) {
+						if ( x < 0 ) {
+							x = width - 1 + x;
+						} else if ( x >= width ) {
+							x = 1 + x - width;
+						}
+					}
+					if ( wrapHeight ) {
+						if ( y < 0 ) {
+							y = height - 1 + y;
+						} else if ( y >= height ) {
+							y = 1 + y - height;
+						}
+					}
+
+					if ( x < 0 || x >= width || y < 0 || y >= height ) {
+						break;					// edge of patch
+					}
+					VectorSubtract( ctrl[y][x].xyz, base, temp );
+					if ( VectorNormalize2( temp, temp ) == 0 ) {
+						continue;				// degenerate edge, get more dist
+					} else {
+						good[k] = qtrue;
+						VectorCopy( temp, around[k] );
+						break;					// good edge
+					}
+				}
+			}
+
+			VectorClear( sum );
+			for ( k = 0 ; k < 8 ; k++ ) {
+				if ( !good[k] || !good[(k+1)&7] ) {
+					continue;	// didn't get two points
+				}
+				CrossProduct( around[(k+1)&7], around[k], normal );
+				if ( VectorNormalize2( normal, normal ) == 0 ) {
+					continue;
+				}
+				VectorAdd( normal, sum, sum );
+				count++;
+			}
+			//if ( count == 0 ) {
+			//	printf("bad normal\n");
+			//}
+			VectorNormalize2( sum, dv->normal );
+		}
+	}
+}
+
+#ifdef USE_VERT_TANGENT_SPACE
+static void MakeMeshTangentVectors(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int numTriangles,
+								   srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2])
+{
+	int             i, j;
+	srfVert_t      *dv[3];
+	static srfVert_t       ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE];
+	srfTriangle_t  *tri;
+
+	// FIXME: use more elegant way
+	for(i = 0; i < width; i++)
+	{
+		for(j = 0; j < height; j++)
+		{
+			dv[0] = &ctrl2[j * width + i];
+			*dv[0] = ctrl[j][i];
+		}
+	}
+
+	for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
+	{
+		dv[0] = &ctrl2[tri->indexes[0]];
+		dv[1] = &ctrl2[tri->indexes[1]];
+		dv[2] = &ctrl2[tri->indexes[2]];
+
+		R_CalcTangentVectors(dv);
+	}
+
+#if 0
+	for(i = 0; i < (width * height); i++)
+	{
+		dv0 = &ctrl2[i];
+
+		VectorNormalize(dv0->normal);
+#if 0
+		VectorNormalize(dv0->tangent);
+		VectorNormalize(dv0->bitangent);
+#else
+		d = DotProduct(dv0->tangent, dv0->normal);
+		VectorMA(dv0->tangent, -d, dv0->normal, dv0->tangent);
+		VectorNormalize(dv0->tangent);
+
+		d = DotProduct(dv0->bitangent, dv0->normal);
+		VectorMA(dv0->bitangent, -d, dv0->normal, dv0->bitangent);
+		VectorNormalize(dv0->bitangent);
+#endif
+	}
+#endif
+
+
+#if 0
+	// do another extra smoothing for normals to avoid flat shading
+	for(i = 0; i < (width * height); i++)
+	{
+		for(j = 0; j < (width * height); j++)
+		{
+			if(R_CompareVert(&ctrl2[i], &ctrl2[j], qfalse))
+			{
+				VectorAdd(ctrl2[i].normal, ctrl2[j].normal, ctrl2[i].normal);
+			}
+		}
+
+		VectorNormalize(ctrl2[i].normal);
+	}
+#endif
+
+	for(i = 0; i < width; i++)
+	{
+		for(j = 0; j < height; j++)
+		{
+			dv[0] = &ctrl2[j * width + i];
+			dv[1] = &ctrl[j][i];
+
+			VectorCopy(dv[0]->tangent, dv[1]->tangent);
+			VectorCopy(dv[0]->bitangent, dv[1]->bitangent);
+		}
+	}
+}
+#endif
+
+
+static int MakeMeshTriangles(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE],
+							 srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2])
+{
+	int             i, j;
+	int             numTriangles;
+	int             w, h;
+	srfVert_t      *dv;
+	static srfVert_t       ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE];
+
+	h = height - 1;
+	w = width - 1;
+	numTriangles = 0;
+	for(i = 0; i < h; i++)
+	{
+		for(j = 0; j < w; j++)
+		{
+			int             v1, v2, v3, v4;
+
+			// vertex order to be reckognized as tristrips
+			v1 = i * width + j + 1;
+			v2 = v1 - 1;
+			v3 = v2 + width;
+			v4 = v3 + 1;
+
+			triangles[numTriangles].indexes[0] = v2;
+			triangles[numTriangles].indexes[1] = v3;
+			triangles[numTriangles].indexes[2] = v1;
+			numTriangles++;
+
+			triangles[numTriangles].indexes[0] = v1;
+			triangles[numTriangles].indexes[1] = v3;
+			triangles[numTriangles].indexes[2] = v4;
+			numTriangles++;
+		}
+	}
+
+	R_CalcSurfaceTriangleNeighbors(numTriangles, triangles);
+
+	// FIXME: use more elegant way
+	for(i = 0; i < width; i++)
+	{
+		for(j = 0; j < height; j++)
+		{
+			dv = &ctrl2[j * width + i];
+			*dv = ctrl[j][i];
+		}
+	}
+
+	R_CalcSurfaceTrianglePlanes(numTriangles, triangles, ctrl2);
+
+	return numTriangles;
+}
+
+
+/*
+============
+InvertCtrl
+============
+*/
+static void InvertCtrl( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+	int		i, j;
+	srfVert_t	temp;
+
+	for ( i = 0 ; i < height ; i++ ) {
+		for ( j = 0 ; j < width/2 ; j++ ) {
+			temp = ctrl[i][j];
+			ctrl[i][j] = ctrl[i][width-1-j];
+			ctrl[i][width-1-j] = temp;
+		}
+	}
+}
+
+
+/*
+=================
+InvertErrorTable
+=================
+*/
+static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) {
+	int		i;
+	float	copy[2][MAX_GRID_SIZE];
+
+	Com_Memcpy( copy, errorTable, sizeof( copy ) );
+
+	for ( i = 0 ; i < width ; i++ ) {
+		errorTable[1][i] = copy[0][i];	//[width-1-i];
+	}
+
+	for ( i = 0 ; i < height ; i++ ) {
+		errorTable[0][i] = copy[1][height-1-i];
+	}
+
+}
+
+/*
+==================
+PutPointsOnCurve
+==================
+*/
+static void PutPointsOnCurve( srfVert_t	ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], 
+							 int width, int height ) {
+	int			i, j;
+	srfVert_t	prev, next;
+
+	for ( i = 0 ; i < width ; i++ ) {
+		for ( j = 1 ; j < height ; j += 2 ) {
+			LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev );
+			LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next );
+			LerpDrawVert( &prev, &next, &ctrl[j][i] );
+		}
+	}
+
+
+	for ( j = 0 ; j < height ; j++ ) {
+		for ( i = 1 ; i < width ; i += 2 ) {
+			LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev );
+			LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next );
+			LerpDrawVert( &prev, &next, &ctrl[j][i] );
+		}
+	}
+}
+
+/*
+=================
+R_CreateSurfaceGridMesh
+=================
+*/
+srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height,
+								srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE],
+								int numTriangles, srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) {
+	int i, j, size;
+	srfVert_t	*vert;
+	vec3_t		tmpVec;
+	srfGridMesh_t *grid;
+
+	// copy the results out to a grid
+	size = (width * height - 1) * sizeof( srfVert_t ) + sizeof( *grid );
+
+#ifdef PATCH_STITCHING
+	grid = /*ri.Hunk_Alloc*/ ri.Malloc( size );
+	Com_Memset(grid, 0, size);
+
+	grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Malloc( width * 4 );
+	Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
+
+	grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 );
+	Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
+
+	grid->numTriangles = numTriangles;
+	grid->triangles = ri.Malloc(grid->numTriangles * sizeof(srfTriangle_t));
+	Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t));
+
+	grid->numVerts = (width * height);
+	grid->verts = ri.Malloc(grid->numVerts * sizeof(srfVert_t));
+#else
+	grid = ri.Hunk_Alloc( size );
+	Com_Memset(grid, 0, size);
+
+	grid->widthLodError = ri.Hunk_Alloc( width * 4 );
+	Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
+
+	grid->heightLodError = ri.Hunk_Alloc( height * 4 );
+	Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
+
+	grid->numTriangles = numTriangles;
+	grid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low);
+	Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t));
+
+	grid->numVerts = (width * height);
+	grid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low);
+#endif
+
+	grid->width = width;
+	grid->height = height;
+	grid->surfaceType = SF_GRID;
+	ClearBounds( grid->meshBounds[0], grid->meshBounds[1] );
+	for ( i = 0 ; i < width ; i++ ) {
+		for ( j = 0 ; j < height ; j++ ) {
+			vert = &grid->verts[j*width+i];
+			*vert = ctrl[j][i];
+			AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] );
+		}
+	}
+
+	// compute local origin and bounds
+	VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin );
+	VectorScale( grid->localOrigin, 0.5f, grid->localOrigin );
+	VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec );
+	grid->meshRadius = VectorLength( tmpVec );
+
+	VectorCopy( grid->localOrigin, grid->lodOrigin );
+	grid->lodRadius = grid->meshRadius;
+	//
+	return grid;
+}
+
+/*
+=================
+R_FreeSurfaceGridMesh
+=================
+*/
+void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) {
+	ri.Free(grid->widthLodError);
+	ri.Free(grid->heightLodError);
+	ri.Free(grid->triangles);
+	ri.Free(grid->verts);
+	ri.Free(grid);
+}
+
+/*
+=================
+R_SubdividePatchToGrid
+=================
+*/
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+								srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) {
+	int			i, j, k, l;
+	srfVert_t_cleared( prev );
+	srfVert_t_cleared( next );
+	srfVert_t_cleared( mid );
+	float		len, maxLen;
+	int			dir;
+	int			t;
+	srfVert_t	ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+	float		errorTable[2][MAX_GRID_SIZE];
+	int			numTriangles;
+	static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2];
+	int consecutiveComplete;
+
+	for ( i = 0 ; i < width ; i++ ) {
+		for ( j = 0 ; j < height ; j++ ) {
+			ctrl[j][i] = points[j*width+i];
+		}
+	}
+
+	for ( dir = 0 ; dir < 2 ; dir++ ) {
+
+		for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) {
+			errorTable[dir][j] = 0;
+		}
+
+		consecutiveComplete = 0;
+
+		// horizontal subdivisions
+		for ( j = 0 ; ; j = (j + 2) % (width - 1) ) {
+			// check subdivided midpoints against control points
+
+			// FIXME: also check midpoints of adjacent patches against the control points
+			// this would basically stitch all patches in the same LOD group together.
+
+			maxLen = 0;
+			for ( i = 0 ; i < height ; i++ ) {
+				vec3_t		midxyz;
+				vec3_t		midxyz2;
+				vec3_t		dir;
+				vec3_t		projected;
+				float		d;
+
+				// calculate the point on the curve
+				for ( l = 0 ; l < 3 ; l++ ) {
+					midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2
+							+ ctrl[i][j+2].xyz[l] ) * 0.25f;
+				}
+
+				// see how far off the line it is
+				// using dist-from-line will not account for internal
+				// texture warping, but it gives a lot less polygons than
+				// dist-from-midpoint
+				VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz );
+				VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir );
+				VectorNormalize( dir );
+
+				d = DotProduct( midxyz, dir );
+				VectorScale( dir, d, projected );
+				VectorSubtract( midxyz, projected, midxyz2);
+				len = VectorLengthSquared( midxyz2 );			// we will do the sqrt later
+				if ( len > maxLen ) {
+					maxLen = len;
+				}
+			}
+
+			maxLen = sqrt(maxLen);
+
+			// if all the points are on the lines, remove the entire columns
+			if ( maxLen < 0.1f ) {
+				errorTable[dir][j+1] = 999;
+				// if we go over the whole grid twice without adding any columns, stop
+				if (++consecutiveComplete >= width)
+					break;
+				continue;
+			}
+
+			// see if we want to insert subdivided columns
+			if ( width + 2 > MAX_GRID_SIZE ) {
+				errorTable[dir][j+1] = 1.0f/maxLen;
+				break;	// can't subdivide any more
+			}
+
+			if ( maxLen <= r_subdivisions->value ) {
+				errorTable[dir][j+1] = 1.0f/maxLen;
+				// if we go over the whole grid twice without adding any columns, stop
+				if (++consecutiveComplete >= width)
+					break;
+				continue;	// didn't need subdivision
+			}
+
+			errorTable[dir][j+2] = 1.0f/maxLen;
+
+			consecutiveComplete = 0;
+
+			// insert two columns and replace the peak
+			width += 2;
+			for ( i = 0 ; i < height ; i++ ) {
+				LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev );
+				LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next );
+				LerpDrawVert( &prev, &next, &mid );
+
+				for ( k = width - 1 ; k > j + 3 ; k-- ) {
+					ctrl[i][k] = ctrl[i][k-2];
+				}
+				ctrl[i][j + 1] = prev;
+				ctrl[i][j + 2] = mid;
+				ctrl[i][j + 3] = next;
+			}
+
+			// skip the new one, we'll get it on the next pass
+			j += 2;
+		}
+
+		Transpose( width, height, ctrl );
+		t = width;
+		width = height;
+		height = t;
+	}
+
+
+	// put all the aproximating points on the curve
+	PutPointsOnCurve( ctrl, width, height );
+
+	// cull out any rows or columns that are colinear
+	for ( i = 1 ; i < width-1 ; i++ ) {
+		if ( errorTable[0][i] != 999 ) {
+			continue;
+		}
+		for ( j = i+1 ; j < width ; j++ ) {
+			for ( k = 0 ; k < height ; k++ ) {
+				ctrl[k][j-1] = ctrl[k][j];
+			}
+			errorTable[0][j-1] = errorTable[0][j];
+		}
+		width--;
+	}
+
+	for ( i = 1 ; i < height-1 ; i++ ) {
+		if ( errorTable[1][i] != 999 ) {
+			continue;
+		}
+		for ( j = i+1 ; j < height ; j++ ) {
+			for ( k = 0 ; k < width ; k++ ) {
+				ctrl[j-1][k] = ctrl[j][k];
+			}
+			errorTable[1][j-1] = errorTable[1][j];
+		}
+		height--;
+	}
+
+#if 1
+	// flip for longest tristrips as an optimization
+	// the results should be visually identical with or
+	// without this step
+	if ( height > width ) {
+		Transpose( width, height, ctrl );
+		InvertErrorTable( errorTable, width, height );
+		t = width;
+		width = height;
+		height = t;
+		InvertCtrl( width, height, ctrl );
+	}
+#endif
+
+	// calculate triangles
+	numTriangles = MakeMeshTriangles(width, height, ctrl, triangles);
+
+	// calculate normals
+	MakeMeshNormals( width, height, ctrl );
+#ifdef USE_VERT_TANGENT_SPACE
+	MakeMeshTangentVectors(width, height, ctrl, numTriangles, triangles);
+#endif
+
+	return R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles);
+}
+
+/*
+===============
+R_GridInsertColumn
+===============
+*/
+srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) {
+	int i, j;
+	int width, height, oldwidth;
+	srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+	float errorTable[2][MAX_GRID_SIZE];
+	float lodRadius;
+	vec3_t lodOrigin;
+	int    numTriangles;
+	static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2];
+
+	oldwidth = 0;
+	width = grid->width + 1;
+	if (width > MAX_GRID_SIZE)
+		return NULL;
+	height = grid->height;
+	for (i = 0; i < width; i++) {
+		if (i == column) {
+			//insert new column
+			for (j = 0; j < grid->height; j++) {
+				LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] );
+				if (j == row)
+					VectorCopy(point, ctrl[j][i].xyz);
+			}
+			errorTable[0][i] = loderror;
+			continue;
+		}
+		errorTable[0][i] = grid->widthLodError[oldwidth];
+		for (j = 0; j < grid->height; j++) {
+			ctrl[j][i] = grid->verts[j * grid->width + oldwidth];
+		}
+		oldwidth++;
+	}
+	for (j = 0; j < grid->height; j++) {
+		errorTable[1][j] = grid->heightLodError[j];
+	}
+	// put all the aproximating points on the curve
+	//PutPointsOnCurve( ctrl, width, height );
+
+	// calculate triangles
+	numTriangles = MakeMeshTriangles(width, height, ctrl, triangles);
+
+	// calculate normals
+	MakeMeshNormals( width, height, ctrl );
+
+	VectorCopy(grid->lodOrigin, lodOrigin);
+	lodRadius = grid->lodRadius;
+	// free the old grid
+	R_FreeSurfaceGridMesh(grid);
+	// create a new grid
+	grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles);
+	grid->lodRadius = lodRadius;
+	VectorCopy(lodOrigin, grid->lodOrigin);
+	return grid;
+}
+
+/*
+===============
+R_GridInsertRow
+===============
+*/
+srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) {
+	int i, j;
+	int width, height, oldheight;
+	srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+	float errorTable[2][MAX_GRID_SIZE];
+	float lodRadius;
+	vec3_t lodOrigin;
+	int             numTriangles;
+	static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2];
+
+	oldheight = 0;
+	width = grid->width;
+	height = grid->height + 1;
+	if (height > MAX_GRID_SIZE)
+		return NULL;
+	for (i = 0; i < height; i++) {
+		if (i == row) {
+			//insert new row
+			for (j = 0; j < grid->width; j++) {
+				LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] );
+				if (j == column)
+					VectorCopy(point, ctrl[i][j].xyz);
+			}
+			errorTable[1][i] = loderror;
+			continue;
+		}
+		errorTable[1][i] = grid->heightLodError[oldheight];
+		for (j = 0; j < grid->width; j++) {
+			ctrl[i][j] = grid->verts[oldheight * grid->width + j];
+		}
+		oldheight++;
+	}
+	for (j = 0; j < grid->width; j++) {
+		errorTable[0][j] = grid->widthLodError[j];
+	}
+	// put all the aproximating points on the curve
+	//PutPointsOnCurve( ctrl, width, height );
+
+	// calculate triangles
+	numTriangles = MakeMeshTriangles(width, height, ctrl, triangles);
+
+	// calculate normals
+	MakeMeshNormals( width, height, ctrl );
+
+	VectorCopy(grid->lodOrigin, lodOrigin);
+	lodRadius = grid->lodRadius;
+	// free the old grid
+	R_FreeSurfaceGridMesh(grid);
+	// create a new grid
+	grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles);
+	grid->lodRadius = lodRadius;
+	VectorCopy(lodOrigin, grid->lodOrigin);
+	return grid;
+}

Added: trunk/code/rend2/tr_extensions.c
===================================================================
--- trunk/code/rend2/tr_extensions.c	                        (rev 0)
+++ trunk/code/rend2/tr_extensions.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,682 @@
+/*
+===========================================================================
+Copyright (C) 2011 James Canete (use.less01 at gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_extensions.c - extensions needed by the renderer not in sdl_glimp.c
+
+#ifdef USE_LOCAL_HEADERS
+#	include "SDL.h"
+#else
+#	include <SDL.h>
+#endif
+
+#include "tr_local.h"
+
+// GL_EXT_draw_range_elements
+void            (APIENTRY * qglDrawRangeElementsEXT) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices);
+
+// GL_EXT_multi_draw_arrays
+void            (APIENTRY * qglMultiDrawArraysEXT) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount);
+void            (APIENTRY * qglMultiDrawElementsEXT) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount);
+
+// GL_ARB_vertex_shader
+void            (APIENTRY * qglBindAttribLocationARB) (GLhandleARB programObj, GLuint index, const GLcharARB * name);
+void            (APIENTRY * qglGetActiveAttribARB) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei * length,
+													GLint * size, GLenum * type, GLcharARB * name);
+GLint(APIENTRY * qglGetAttribLocationARB) (GLhandleARB programObj, const GLcharARB * name);
+
+// GL_ARB_vertex_program
+void            (APIENTRY * qglVertexAttrib4fARB) (GLuint, GLfloat, GLfloat, GLfloat, GLfloat);
+void            (APIENTRY * qglVertexAttrib4fvARB) (GLuint, const GLfloat *);
+void            (APIENTRY * qglVertexAttribPointerARB) (GLuint index, GLint size, GLenum type, GLboolean normalized,
+														GLsizei stride, const GLvoid * pointer);
+void            (APIENTRY * qglEnableVertexAttribArrayARB) (GLuint index);
+void            (APIENTRY * qglDisableVertexAttribArrayARB) (GLuint index);
+
+// GL_ARB_vertex_buffer_object
+void (APIENTRY * qglBindBufferARB) (GLenum target, GLuint buffer);
+void (APIENTRY * qglDeleteBuffersARB) (GLsizei n, const GLuint * buffers);
+void (APIENTRY * qglGenBuffersARB) (GLsizei n, GLuint * buffers);
+
+GLboolean(APIENTRY * qglIsBufferARB) (GLuint buffer);
+void (APIENTRY * qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage);
+void (APIENTRY * qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid * data);
+void (APIENTRY * qglGetBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid * data);
+
+void (APIENTRY * qglGetBufferParameterivARB) (GLenum target, GLenum pname, GLint * params);
+void (APIENTRY * qglGetBufferPointervARB) (GLenum target, GLenum pname, GLvoid * *params);
+
+// GL_ARB_shader_objects
+void            (APIENTRY * qglDeleteObjectARB) (GLhandleARB obj);
+
+GLhandleARB(APIENTRY * qglGetHandleARB) (GLenum pname);
+void            (APIENTRY * qglDetachObjectARB) (GLhandleARB containerObj, GLhandleARB attachedObj);
+
+GLhandleARB(APIENTRY * qglCreateShaderObjectARB) (GLenum shaderType);
+void            (APIENTRY * qglShaderSourceARB) (GLhandleARB shaderObj, GLsizei count, const GLcharARB * *string,
+												 const GLint * length);
+void            (APIENTRY * qglCompileShaderARB) (GLhandleARB shaderObj);
+
+GLhandleARB(APIENTRY * qglCreateProgramObjectARB) (void);
+void            (APIENTRY * qglAttachObjectARB) (GLhandleARB containerObj, GLhandleARB obj);
+void            (APIENTRY * qglLinkProgramARB) (GLhandleARB programObj);
+void            (APIENTRY * qglUseProgramObjectARB) (GLhandleARB programObj);
+void            (APIENTRY * qglValidateProgramARB) (GLhandleARB programObj);
+void            (APIENTRY * qglUniform1fARB) (GLint location, GLfloat v0);
+void            (APIENTRY * qglUniform2fARB) (GLint location, GLfloat v0, GLfloat v1);
+void            (APIENTRY * qglUniform3fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
+void            (APIENTRY * qglUniform4fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+void            (APIENTRY * qglUniform1iARB) (GLint location, GLint v0);
+void            (APIENTRY * qglUniform2iARB) (GLint location, GLint v0, GLint v1);
+void            (APIENTRY * qglUniform3iARB) (GLint location, GLint v0, GLint v1, GLint v2);
+void            (APIENTRY * qglUniform4iARB) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
+void            (APIENTRY * qglUniform1fvARB) (GLint location, GLsizei count, const GLfloat * value);
+void            (APIENTRY * qglUniform2fvARB) (GLint location, GLsizei count, const GLfloat * value);
+void            (APIENTRY * qglUniform3fvARB) (GLint location, GLsizei count, const GLfloat * value);
+void            (APIENTRY * qglUniform4fvARB) (GLint location, GLsizei count, const GLfloat * value);
+void            (APIENTRY * qglUniform2ivARB) (GLint location, GLsizei count, const GLint * value);
+void            (APIENTRY * qglUniform3ivARB) (GLint location, GLsizei count, const GLint * value);
+void            (APIENTRY * qglUniform4ivARB) (GLint location, GLsizei count, const GLint * value);
+void            (APIENTRY * qglUniformMatrix2fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+void            (APIENTRY * qglUniformMatrix3fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+void            (APIENTRY * qglUniformMatrix4fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value);
+void            (APIENTRY * qglGetObjectParameterfvARB) (GLhandleARB obj, GLenum pname, GLfloat * params);
+void            (APIENTRY * qglGetObjectParameterivARB) (GLhandleARB obj, GLenum pname, GLint * params);
+void            (APIENTRY * qglGetInfoLogARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog);
+void            (APIENTRY * qglGetAttachedObjectsARB) (GLhandleARB containerObj, GLsizei maxCount, GLsizei * count,
+													   GLhandleARB * obj);
+GLint(APIENTRY * qglGetUniformLocationARB) (GLhandleARB programObj, const GLcharARB * name);
+void            (APIENTRY * qglGetActiveUniformARB) (GLhandleARB programObj, GLuint index, GLsizei maxIndex, GLsizei * length,
+													 GLint * size, GLenum * type, GLcharARB * name);
+void            (APIENTRY * qglGetUniformfvARB) (GLhandleARB programObj, GLint location, GLfloat * params);
+void            (APIENTRY * qglGetUniformivARB) (GLhandleARB programObj, GLint location, GLint * params);
+void            (APIENTRY * qglGetShaderSourceARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * source);
+
+// GL_ARB_texture_compression
+void (APIENTRY * qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, 
+	GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
+	GLint border, GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
+	GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
+	GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
+	GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, 
+	GLsizei imageSize, const GLvoid *data);
+void (APIENTRY * qglGetCompressedTexImageARB)(GLenum target, GLint lod,
+	GLvoid *img);
+
+// GL_EXT_framebuffer_object
+GLboolean (APIENTRY * qglIsRenderbufferEXT)(GLuint renderbuffer);
+void (APIENTRY * qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer);
+void (APIENTRY * qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers);
+void (APIENTRY * qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers);
+
+void (APIENTRY * qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
+
+void (APIENTRY * qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params);
+
+GLboolean (APIENTRY * qglIsFramebufferEXT)(GLuint framebuffer);
+void (APIENTRY * qglBindFramebufferEXT)(GLenum target, GLuint framebuffer);
+void (APIENTRY * qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers);
+void (APIENTRY * qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers);
+
+GLenum (APIENTRY * qglCheckFramebufferStatusEXT)(GLenum target);
+
+void (APIENTRY * qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level);
+void (APIENTRY * qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level);
+void (APIENTRY * qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture,
+	GLint level, GLint zoffset);
+
+void (APIENTRY * qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget,
+	GLuint renderbuffer);
+
+void (APIENTRY * qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params);
+
+void (APIENTRY * qglGenerateMipmapEXT)(GLenum target);
+
+// GL_ARB_occlusion_query
+void (APIENTRY * qglGenQueriesARB)(GLsizei n, GLuint *ids);
+void (APIENTRY * qglDeleteQueriesARB)(GLsizei n, const GLuint *ids);
+GLboolean (APIENTRY * qglIsQueryARB)(GLuint id);
+void (APIENTRY * qglBeginQueryARB)(GLenum target, GLuint id);
+void (APIENTRY * qglEndQueryARB)(GLenum target);
+void (APIENTRY * qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params);
+void (APIENTRY * qglGetQueryObjectivARB)(GLuint id, GLenum pname, GLint *params);
+void (APIENTRY * qglGetQueryObjectuivARB)(GLuint id, GLenum pname, GLuint *params);
+
+// GL_EXT_framebuffer_blit
+void (APIENTRY * qglBlitFramebufferEXT)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
+                            GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
+                            GLbitfield mask, GLenum filter);
+
+// GL_EXT_framebuffer_multisample
+void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei samples,
+	GLenum internalformat, GLsizei width, GLsizei height);
+
+// GL_ARB_draw_buffers
+void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs);
+
+static qboolean GLimp_HaveExtension(const char *ext)
+{
+	const char *ptr = Q_stristr( glConfig.extensions_string, ext );
+	if (ptr == NULL)
+		return qfalse;
+	ptr += strlen(ext);
+	return ((*ptr == ' ') || (*ptr == '\0'));  // verify it's complete string.
+}
+
+void GLimp_InitExtraExtensions()
+{
+	char *extension;
+	const char* result[3] = { "...ignoring %s\n", "...using %s\n", "...%s not found\n" };
+
+	// GL_EXT_draw_range_elements
+	extension = "GL_EXT_draw_range_elements";
+	glRefConfig.drawRangeElements = qfalse;
+	qglMultiDrawArraysEXT = NULL;
+	qglMultiDrawElementsEXT = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglDrawRangeElementsEXT = (void *) SDL_GL_GetProcAddress("glDrawRangeElementsEXT");
+
+		if ( r_ext_draw_range_elements->integer)
+			glRefConfig.drawRangeElements = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.drawRangeElements], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_multi_draw_arrays
+	extension = "GL_EXT_multi_draw_arrays";
+	glRefConfig.multiDrawArrays = qfalse;
+	qglMultiDrawArraysEXT = NULL;
+	qglMultiDrawElementsEXT = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglMultiDrawArraysEXT = (PFNGLMULTIDRAWARRAYSEXTPROC) SDL_GL_GetProcAddress("glMultiDrawArraysEXT");
+		qglMultiDrawElementsEXT = (PFNGLMULTIDRAWELEMENTSEXTPROC) SDL_GL_GetProcAddress("glMultiDrawElementsEXT");
+
+		if ( r_ext_multi_draw_arrays->integer )
+			glRefConfig.multiDrawArrays = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.multiDrawArrays], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_vertex_program
+	//glRefConfig.vertexProgram = qfalse;
+	extension = "GL_ARB_vertex_program";
+	qglVertexAttrib4fARB = NULL;
+	qglVertexAttrib4fvARB = NULL;
+	qglVertexAttribPointerARB = NULL;
+	qglEnableVertexAttribArrayARB = NULL;
+	qglDisableVertexAttribArrayARB = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglVertexAttrib4fARB = (PFNGLVERTEXATTRIB4FARBPROC) SDL_GL_GetProcAddress("glVertexAttrib4fARB");
+		qglVertexAttrib4fvARB = (PFNGLVERTEXATTRIB4FVARBPROC) SDL_GL_GetProcAddress("glVertexAttrib4fvARB");
+		qglVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC) SDL_GL_GetProcAddress("glVertexAttribPointerARB");
+		qglEnableVertexAttribArrayARB =
+			(PFNGLENABLEVERTEXATTRIBARRAYARBPROC) SDL_GL_GetProcAddress("glEnableVertexAttribArrayARB");
+		qglDisableVertexAttribArrayARB =
+			(PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) SDL_GL_GetProcAddress("glDisableVertexAttribArrayARB");
+
+		ri.Printf(PRINT_ALL, result[1], extension);
+		//glRefConfig.vertexProgram = qtrue;
+	}
+	else
+	{
+		ri.Error(ERR_FATAL, result[2], extension);
+	}
+	
+	// GL_ARB_vertex_buffer_object
+	//glRefConfig.vertexBufferObject = qfalse;
+	extension = "GL_ARB_vertex_buffer_object";
+	qglBindBufferARB = NULL;
+	qglDeleteBuffersARB = NULL;
+	qglGenBuffersARB = NULL;
+	qglIsBufferARB = NULL;
+	qglBufferDataARB = NULL;
+	qglBufferSubDataARB = NULL;
+	qglGetBufferSubDataARB = NULL;
+	qglGetBufferParameterivARB = NULL;
+	qglGetBufferPointervARB = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglBindBufferARB = (PFNGLBINDBUFFERARBPROC) SDL_GL_GetProcAddress("glBindBufferARB");
+		qglDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB");
+		qglGenBuffersARB = (PFNGLGENBUFFERSARBPROC) SDL_GL_GetProcAddress("glGenBuffersARB");
+		qglIsBufferARB = (PFNGLISBUFFERARBPROC) SDL_GL_GetProcAddress("glIsBufferARB");
+		qglBufferDataARB = (PFNGLBUFFERDATAARBPROC) SDL_GL_GetProcAddress("glBufferDataARB");
+		qglBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glBufferSubDataARB");
+		qglGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glGetBufferSubDataARB");
+		qglGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetBufferParameterivARB");
+		qglGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC) SDL_GL_GetProcAddress("glGetBufferPointervARB");
+		ri.Printf(PRINT_ALL, result[1], extension);
+		//glRefConfig.vertexBufferObject = qtrue;
+	}
+	else
+	{
+		ri.Error(ERR_FATAL, result[2], extension);
+	}
+
+	// GL_ARB_shader_objects
+	extension = "GL_ARB_shader_objects";
+	//glRefConfig.shaderObjects = qfalse;
+	qglDeleteObjectARB = NULL;
+	qglGetHandleARB = NULL;
+	qglDetachObjectARB = NULL;
+	qglCreateShaderObjectARB = NULL;
+	qglShaderSourceARB = NULL;
+	qglCompileShaderARB = NULL;
+	qglCreateProgramObjectARB = NULL;
+	qglAttachObjectARB = NULL;
+	qglLinkProgramARB = NULL;
+	qglUseProgramObjectARB = NULL;
+	qglValidateProgramARB = NULL;
+	qglUniform1fARB = NULL;
+	qglUniform2fARB = NULL;
+	qglUniform3fARB = NULL;
+	qglUniform4fARB = NULL;
+	qglUniform1iARB = NULL;
+	qglUniform2iARB = NULL;
+	qglUniform3iARB = NULL;
+	qglUniform4iARB = NULL;
+	qglUniform1fvARB = NULL;
+	qglUniform2fvARB = NULL;
+	qglUniform3fvARB = NULL;
+	qglUniform4fvARB = NULL;
+	qglUniform2ivARB = NULL;
+	qglUniform3ivARB = NULL;
+	qglUniform4ivARB = NULL;
+	qglUniformMatrix2fvARB = NULL;
+	qglUniformMatrix3fvARB = NULL;
+	qglUniformMatrix4fvARB = NULL;
+	qglGetObjectParameterfvARB = NULL;
+	qglGetObjectParameterivARB = NULL;
+	qglGetInfoLogARB = NULL;
+	qglGetAttachedObjectsARB = NULL;
+	qglGetUniformLocationARB = NULL;
+	qglGetActiveUniformARB = NULL;
+	qglGetUniformfvARB = NULL;
+	qglGetUniformivARB = NULL;
+	qglGetShaderSourceARB = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB");
+		qglGetHandleARB = (PFNGLGETHANDLEARBPROC) SDL_GL_GetProcAddress("glGetHandleARB");
+		qglDetachObjectARB = (PFNGLDETACHOBJECTARBPROC) SDL_GL_GetProcAddress("glDetachObjectARB");
+		qglCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB");
+		qglShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB");
+		qglCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB");
+		qglCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB");
+		qglAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB");
+		qglLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB");
+		qglUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB");
+		qglValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC) SDL_GL_GetProcAddress("glValidateProgramARB");
+		qglUniform1fARB = (PFNGLUNIFORM1FARBPROC) SDL_GL_GetProcAddress("glUniform1fARB");
+		qglUniform2fARB = (PFNGLUNIFORM2FARBPROC) SDL_GL_GetProcAddress("glUniform2fARB");
+		qglUniform3fARB = (PFNGLUNIFORM3FARBPROC) SDL_GL_GetProcAddress("glUniform3fARB");
+		qglUniform4fARB = (PFNGLUNIFORM4FARBPROC) SDL_GL_GetProcAddress("glUniform4fARB");
+		qglUniform1iARB = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB");
+		qglUniform2iARB = (PFNGLUNIFORM2IARBPROC) SDL_GL_GetProcAddress("glUniform2iARB");
+		qglUniform3iARB = (PFNGLUNIFORM3IARBPROC) SDL_GL_GetProcAddress("glUniform3iARB");
+		qglUniform4iARB = (PFNGLUNIFORM4IARBPROC) SDL_GL_GetProcAddress("glUniform4iARB");
+		qglUniform1fvARB = (PFNGLUNIFORM1FVARBPROC) SDL_GL_GetProcAddress("glUniform1fvARB");
+		qglUniform2fvARB = (PFNGLUNIFORM2FVARBPROC) SDL_GL_GetProcAddress("glUniform2fvARB");
+		qglUniform3fvARB = (PFNGLUNIFORM3FVARBPROC) SDL_GL_GetProcAddress("glUniform3fvARB");
+		qglUniform4fvARB = (PFNGLUNIFORM4FVARBPROC) SDL_GL_GetProcAddress("glUniform4fvARB");
+		qglUniform2ivARB = (PFNGLUNIFORM2IVARBPROC) SDL_GL_GetProcAddress("glUniform2ivARB");
+		qglUniform3ivARB = (PFNGLUNIFORM3IVARBPROC) SDL_GL_GetProcAddress("glUniform3ivARB");
+		qglUniform4ivARB = (PFNGLUNIFORM4IVARBPROC) SDL_GL_GetProcAddress("glUniform4ivARB");
+		qglUniformMatrix2fvARB = (PFNGLUNIFORMMATRIX2FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix2fvARB");
+		qglUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix3fvARB");
+		qglUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix4fvARB");
+		qglGetObjectParameterfvARB = (PFNGLGETOBJECTPARAMETERFVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterfvARB");
+		qglGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB");
+		qglGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB");
+		qglGetAttachedObjectsARB = (PFNGLGETATTACHEDOBJECTSARBPROC) SDL_GL_GetProcAddress("glGetAttachedObjectsARB");
+		qglGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB");
+		qglGetActiveUniformARB = (PFNGLGETACTIVEUNIFORMARBPROC) SDL_GL_GetProcAddress("glGetActiveUniformARB");
+		qglGetUniformfvARB = (PFNGLGETUNIFORMFVARBPROC) SDL_GL_GetProcAddress("glGetUniformfvARB");
+		qglGetUniformivARB = (PFNGLGETUNIFORMIVARBPROC) SDL_GL_GetProcAddress("glGetUniformivARB");
+		qglGetShaderSourceARB = (PFNGLGETSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glGetShaderSourceARB");
+		ri.Printf(PRINT_ALL, result[1], extension);
+		//glRefConfig.shaderObjects = qtrue;
+	}
+	else
+	{
+		ri.Error(ERR_FATAL, result[2], extension);
+	}
+
+	// GL_ARB_vertex_shader
+	//glRefConfig.vertexShader = qfalse;
+	extension = "GL_ARB_vertex_shader";
+	qglBindAttribLocationARB = NULL;
+	qglGetActiveAttribARB = NULL;
+	qglGetAttribLocationARB = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		//int				reservedComponents;
+
+		//qglGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.maxVertexUniforms);
+		//qglGetIntegerv(GL_MAX_VARYING_FLOATS_ARB, &glConfig.maxVaryingFloats);
+		//qglGetIntegerv(GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.maxVertexAttribs);
+
+		//reservedComponents = 16 * 10; // approximation how many uniforms we have besides the bone matrices
+
+#if 0
+		if(glConfig.driverType == GLDRV_MESA)
+		{
+			// HACK
+			// restrict to number of vertex uniforms to 512 because of:
+			// xreal.x86_64: nv50_program.c:4181: nv50_program_validate_data: Assertion `p->param_nr <= 512' failed
+
+			glConfig.maxVertexUniforms = Q_bound(0, glConfig.maxVertexUniforms, 512);
+		}
+#endif
+
+		//glConfig.maxVertexSkinningBones = (int) Q_bound(0.0, (Q_max(glConfig.maxVertexUniforms - reservedComponents, 0) / 16), MAX_BONES);
+		//glConfig.vboVertexSkinningAvailable = r_vboVertexSkinning->integer && ((glConfig.maxVertexSkinningBones >= 12) ? qtrue : qfalse);
+
+		qglBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC) SDL_GL_GetProcAddress("glBindAttribLocationARB");
+		qglGetActiveAttribARB = (PFNGLGETACTIVEATTRIBARBPROC) SDL_GL_GetProcAddress("glGetActiveAttribARB");
+		qglGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetAttribLocationARB");
+		ri.Printf(PRINT_ALL, result[1], extension);
+		//glRefConfig.vertexShader = qtrue;
+	}
+	else
+	{
+		ri.Error(ERR_FATAL, result[2], extension);
+	}
+
+	// GL_ARB_shading_language_100
+	extension = "GL_ARB_shading_language_100";
+	glRefConfig.textureFloat = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		char version[256];
+
+		Q_strncpyz( version, (char *) qglGetString (GL_SHADING_LANGUAGE_VERSION_ARB), sizeof( version ) );
+
+		sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
+
+		ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
+	}
+	else
+	{
+		ri.Error(ERR_FATAL, result[2], extension);
+	}
+
+	glRefConfig.memInfo = MI_NONE;
+
+	if( GLimp_HaveExtension( "GL_NVX_gpu_memory_info" ) )
+	{
+		glRefConfig.memInfo = MI_NVX;
+	}
+	else if( GLimp_HaveExtension( "GL_ATI_meminfo" ) )
+	{
+		glRefConfig.memInfo = MI_ATI;
+	}
+
+	extension = "GL_ARB_texture_non_power_of_two";
+	glRefConfig.textureNonPowerOfTwo = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		if(1) //(r_ext_texture_non_power_of_two->integer)
+		{
+			glRefConfig.textureNonPowerOfTwo = qtrue;
+		}
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.textureNonPowerOfTwo], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_texture_float
+	extension = "GL_ARB_texture_float";
+	glRefConfig.textureFloat = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		if( r_ext_texture_float->integer )
+		{
+			glRefConfig.textureFloat = qtrue;
+		}
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.textureFloat], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_half_float_pixel
+	extension = "GL_ARB_half_float_pixel";
+	glRefConfig.halfFloatPixel = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		if( r_arb_half_float_pixel->integer )
+			glRefConfig.halfFloatPixel = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.halfFloatPixel], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_framebuffer_object
+	extension = "GL_EXT_framebuffer_object";
+	glRefConfig.framebufferObject = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &glRefConfig.maxRenderbufferSize);
+		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &glRefConfig.maxColorAttachments);
+
+		qglIsRenderbufferEXT = (PFNGLISRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glIsRenderbufferEXT");
+		qglBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glBindRenderbufferEXT");
+		qglDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC) SDL_GL_GetProcAddress("glDeleteRenderbuffersEXT");
+		qglGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC) SDL_GL_GetProcAddress("glGenRenderbuffersEXT");
+		qglRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC) SDL_GL_GetProcAddress("glRenderbufferStorageEXT");
+		qglGetRenderbufferParameterivEXT = (PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) SDL_GL_GetProcAddress("glGetRenderbufferParameterivEXT");
+		qglIsFramebufferEXT = (PFNGLISFRAMEBUFFEREXTPROC) SDL_GL_GetProcAddress("glIsFramebufferEXT");
+		qglBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) SDL_GL_GetProcAddress("glBindFramebufferEXT");
+		qglDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC) SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
+		qglGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC) SDL_GL_GetProcAddress("glGenFramebuffersEXT");
+		qglCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
+		qglFramebufferTexture1DEXT = (PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture1DEXT");
+		qglFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
+		qglFramebufferTexture3DEXT = (PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture3DEXT");
+		qglFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glFramebufferRenderbufferEXT");
+		qglGetFramebufferAttachmentParameterivEXT = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) SDL_GL_GetProcAddress("glGetFramebufferAttachmentParameterivEXT");
+		qglGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC) SDL_GL_GetProcAddress("glGenerateMipmapEXT");
+
+		if(r_ext_framebuffer_object->value)
+			glRefConfig.framebufferObject = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_packed_depth_stencil
+	extension = "GL_EXT_packed_depth_stencil";
+	glRefConfig.packedDepthStencil = qfalse;
+	if( GLimp_HaveExtension(extension))
+	{
+		glRefConfig.packedDepthStencil = qtrue;
+		ri.Printf(PRINT_ALL, result[glRefConfig.packedDepthStencil], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_occlusion_query
+	extension = "GL_ARB_occlusion_query";
+	glRefConfig.occlusionQuery = qfalse;
+	if (GLimp_HaveExtension(extension))
+	{
+		qglGenQueriesARB = (PFNGLGENQUERIESARBPROC) SDL_GL_GetProcAddress("glGenQueriesARB");
+		qglDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC) SDL_GL_GetProcAddress("glDeleteQueriesARB");
+		qglIsQueryARB = (PFNGLISQUERYARBPROC) SDL_GL_GetProcAddress("glIsQueryARB");
+		qglBeginQueryARB = (PFNGLBEGINQUERYARBPROC) SDL_GL_GetProcAddress("glBeginQueryARB");
+		qglEndQueryARB = (PFNGLENDQUERYARBPROC) SDL_GL_GetProcAddress("glEndQueryARB");
+		qglGetQueryivARB = (PFNGLGETQUERYIVARBPROC) SDL_GL_GetProcAddress("glGetQueryivARB");
+		qglGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC) SDL_GL_GetProcAddress("glGetQueryObjectivARB");
+		qglGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC) SDL_GL_GetProcAddress("glGetQueryObjectuivARB");
+		glRefConfig.occlusionQuery = qtrue;
+		ri.Printf(PRINT_ALL, result[glRefConfig.occlusionQuery], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_framebuffer_blit
+	extension = "GL_EXT_framebuffer_blit";
+	glRefConfig.framebufferBlit = qfalse;
+	if (GLimp_HaveExtension(extension))
+	{
+		qglBlitFramebufferEXT = (void *)SDL_GL_GetProcAddress("glBlitFramebufferEXT");
+		glRefConfig.framebufferBlit = qtrue;
+		ri.Printf(PRINT_ALL, result[glRefConfig.framebufferBlit], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_framebuffer_multisample
+	extension = "GL_EXT_framebuffer_multisample";
+	glRefConfig.framebufferMultisample = qfalse;
+	if (GLimp_HaveExtension(extension))
+	{
+		qglRenderbufferStorageMultisampleEXT = (void *)SDL_GL_GetProcAddress("glRenderbufferStorageMultisampleEXT");
+		glRefConfig.framebufferMultisample = qtrue;
+		ri.Printf(PRINT_ALL, result[glRefConfig.framebufferMultisample], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_texture_sRGB
+	extension = "GL_EXT_texture_sRGB";
+	glRefConfig.texture_srgb = qfalse;
+	if (GLimp_HaveExtension(extension))
+	{
+		if (r_srgb->integer)
+			glRefConfig.texture_srgb = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.texture_srgb], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_EXT_framebuffer_sRGB
+	extension = "GL_EXT_framebuffer_sRGB";
+	glRefConfig.framebuffer_srgb = qfalse;
+	if (GLimp_HaveExtension(extension))
+	{
+		if (r_srgb->integer)
+			glRefConfig.framebuffer_srgb = qtrue;
+
+		ri.Printf(PRINT_ALL, result[glRefConfig.framebuffer_srgb], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	glRefConfig.textureCompression = TCR_NONE;
+
+	// GL_EXT_texture_compression_latc
+	extension = "GL_EXT_texture_compression_latc";
+	if (GLimp_HaveExtension(extension))
+	{
+		if (r_ext_compressed_textures->integer)
+			glRefConfig.textureCompression |= TCR_LATC;
+
+		ri.Printf(PRINT_ALL, result[r_ext_compressed_textures->integer ? 1 : 0], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_texture_compression_bptc
+	extension = "GL_ARB_texture_compression_bptc";
+	if (GLimp_HaveExtension(extension))
+	{
+		if (r_ext_compressed_textures->integer >= 2)
+			glRefConfig.textureCompression |= TCR_BPTC;
+
+		ri.Printf(PRINT_ALL, result[(r_ext_compressed_textures->integer >= 2) ? 1 : 0], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_draw_buffers
+	extension = "GL_ARB_draw_buffers";
+	qglDrawBuffersARB = NULL;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		qglDrawBuffersARB = (void *) SDL_GL_GetProcAddress("glDrawBuffersARB");
+
+		ri.Printf(PRINT_ALL, result[1], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+
+	// GL_ARB_depth_clamp
+	extension = "GL_ARB_depth_clamp";
+	glRefConfig.depthClamp = qfalse;
+	if( GLimp_HaveExtension( extension ) )
+	{
+		glRefConfig.depthClamp = qtrue;
+		ri.Printf(PRINT_ALL, result[1], extension);
+	}
+	else
+	{
+		ri.Printf(PRINT_ALL, result[2], extension);
+	}
+}

Added: trunk/code/rend2/tr_extramath.c
===================================================================
--- trunk/code/rend2/tr_extramath.c	                        (rev 0)
+++ trunk/code/rend2/tr_extramath.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,240 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01 at gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_extramath.c - extra math needed by the renderer not in qmath.c
+
+#include "tr_local.h"
+
+// Some matrix helper functions
+// FIXME: do these already exist in ioq3 and I don't know about them?
+
+void Matrix16Zero( matrix_t out )
+{
+	out[ 0] = 0.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f;
+	out[ 1] = 0.0f; out[ 5] = 0.0f; out[ 9] = 0.0f; out[13] = 0.0f;
+	out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 0.0f; out[14] = 0.0f;
+	out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 0.0f;
+}
+
+void Matrix16Identity( matrix_t out )
+{
+	out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f;
+	out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = 0.0f;
+	out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = 0.0f;
+	out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f;
+}
+
+void Matrix16Copy( const matrix_t in, matrix_t out )
+{
+	out[ 0] = in[ 0]; out[ 4] = in[ 4]; out[ 8] = in[ 8]; out[12] = in[12]; 
+	out[ 1] = in[ 1]; out[ 5] = in[ 5]; out[ 9] = in[ 9]; out[13] = in[13]; 
+	out[ 2] = in[ 2]; out[ 6] = in[ 6]; out[10] = in[10]; out[14] = in[14]; 
+	out[ 3] = in[ 3]; out[ 7] = in[ 7]; out[11] = in[11]; out[15] = in[15]; 
+}
+
+void Matrix16Multiply( const matrix_t in1, const matrix_t in2, matrix_t out )
+{
+	out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3];
+	out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3];
+	out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3];
+	out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3];
+
+	out[ 4] = in1[ 0] * in2[ 4] + in1[ 4] * in2[ 5] + in1[ 8] * in2[ 6] + in1[12] * in2[ 7];
+	out[ 5] = in1[ 1] * in2[ 4] + in1[ 5] * in2[ 5] + in1[ 9] * in2[ 6] + in1[13] * in2[ 7];
+	out[ 6] = in1[ 2] * in2[ 4] + in1[ 6] * in2[ 5] + in1[10] * in2[ 6] + in1[14] * in2[ 7];
+	out[ 7] = in1[ 3] * in2[ 4] + in1[ 7] * in2[ 5] + in1[11] * in2[ 6] + in1[15] * in2[ 7];
+
+	out[ 8] = in1[ 0] * in2[ 8] + in1[ 4] * in2[ 9] + in1[ 8] * in2[10] + in1[12] * in2[11];
+	out[ 9] = in1[ 1] * in2[ 8] + in1[ 5] * in2[ 9] + in1[ 9] * in2[10] + in1[13] * in2[11];
+	out[10] = in1[ 2] * in2[ 8] + in1[ 6] * in2[ 9] + in1[10] * in2[10] + in1[14] * in2[11];
+	out[11] = in1[ 3] * in2[ 8] + in1[ 7] * in2[ 9] + in1[11] * in2[10] + in1[15] * in2[11];
+
+	out[12] = in1[ 0] * in2[12] + in1[ 4] * in2[13] + in1[ 8] * in2[14] + in1[12] * in2[15];
+	out[13] = in1[ 1] * in2[12] + in1[ 5] * in2[13] + in1[ 9] * in2[14] + in1[13] * in2[15];
+	out[14] = in1[ 2] * in2[12] + in1[ 6] * in2[13] + in1[10] * in2[14] + in1[14] * in2[15];
+	out[15] = in1[ 3] * in2[12] + in1[ 7] * in2[13] + in1[11] * in2[14] + in1[15] * in2[15];
+}
+
+void Matrix16Transform( const matrix_t in1, const vec4_t in2, vec4_t out )
+{
+	out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3];
+	out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3];
+	out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3];
+	out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3];
+}
+
+qboolean Matrix16Compare( const matrix_t a, const matrix_t b )
+{
+	return !(a[ 0] != b[ 0] || a[ 4] != b[ 4] || a[ 8] != b[ 8] || a[12] != b[12] ||
+             a[ 1] != b[ 1] || a[ 5] != b[ 5] || a[ 9] != b[ 9] || a[13] != b[13] ||
+		     a[ 2] != b[ 2] || a[ 6] != b[ 6] || a[10] != b[10] || a[14] != b[14] ||
+		     a[ 3] != b[ 3] || a[ 7] != b[ 7] || a[11] != b[11] || a[15] != b[15]);
+}
+
+void Matrix16Dump( const matrix_t in )
+{
+	ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 0], in[ 4], in[ 8], in[12]);
+	ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 1], in[ 5], in[ 9], in[13]);
+	ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 2], in[ 6], in[10], in[14]);
+	ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 3], in[ 7], in[11], in[15]);
+}
+
+void Matrix16Translation( vec3_t vec, matrix_t out )
+{
+	out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = vec[0];
+	out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = vec[1];
+	out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = vec[2];
+	out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f;
+}
+
+void Matrix16Ortho( float left, float right, float bottom, float top, float znear, float zfar, matrix_t out )
+{
+	out[ 0] = 2.0f / (right - left); out[ 4] = 0.0f;                  out[ 8] = 0.0f;                  out[12] = -(right + left) / (right - left);
+	out[ 1] = 0.0f;                  out[ 5] = 2.0f / (top - bottom); out[ 9] = 0.0f;                  out[13] = -(top + bottom) / (top - bottom);
+	out[ 2] = 0.0f;                  out[ 6] = 0.0f;                  out[10] = 2.0f / (zfar - znear); out[14] = -(zfar + znear) / (zfar - znear);
+	out[ 3] = 0.0f;                  out[ 7] = 0.0f;                  out[11] = 0.0f;                  out[15] = 1.0f;
+}
+
+void Matrix16View(vec3_t axes[3], vec3_t origin, matrix_t out)
+{
+	out[0]  = axes[0][0];
+	out[1]  = axes[1][0];
+	out[2]  = axes[2][0];
+	out[3]  = 0;
+
+	out[4]  = axes[0][1];
+	out[5]  = axes[1][1];
+	out[6]  = axes[2][1];
+	out[7]  = 0;
+
+	out[8]  = axes[0][2];
+	out[9]  = axes[1][2];
+	out[10] = axes[2][2];
+	out[11] = 0;
+
+	out[12] = -DotProduct(origin, axes[0]);
+	out[13] = -DotProduct(origin, axes[1]);
+	out[14] = -DotProduct(origin, axes[2]);
+	out[15] = 1;
+}
+
+void Matrix16SimpleInverse( const matrix_t in, matrix_t out)
+{
+	vec3_t v;
+	float invSqrLen;
+ 
+	VectorCopy(in + 0, v);
+	invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+	out[ 0] = v[0]; out[ 4] = v[1]; out[ 8] = v[2]; out[12] = -DotProduct(v, &in[12]);
+
+	VectorCopy(in + 4, v);
+	invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+	out[ 1] = v[0]; out[ 5] = v[1]; out[ 9] = v[2]; out[13] = -DotProduct(v, &in[12]);
+
+	VectorCopy(in + 8, v);
+	invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+	out[ 2] = v[0]; out[ 6] = v[1]; out[10] = v[2]; out[14] = -DotProduct(v, &in[12]);
+
+	out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f;
+}
+
+void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c)
+{
+	c[0] = a[0] * (1.0f - lerp) + b[0] * lerp;
+	c[1] = a[1] * (1.0f - lerp) + b[1] * lerp;
+	c[2] = a[2] * (1.0f - lerp) + b[2] * lerp;
+}
+
+qboolean SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2)
+{
+	float radiusSum = radius1 + radius2;
+	vec3_t diff;
+	
+	VectorSubtract(origin1, origin2, diff);
+
+	if (DotProduct(diff, diff) <= radiusSum * radiusSum)
+	{
+		return qtrue;
+	}
+
+	return qfalse;
+}
+
+void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3)
+{
+	vec3_t diff;
+
+	VectorScale(origin1, 0.5f, origin3);
+	VectorMA(origin3, 0.5f, origin2, origin3);
+
+	VectorSubtract(origin1, origin2, diff);
+	*radius3 = VectorLength(diff) * 0.5f + MAX(radius1, radius2);
+}
+
+int NextPowerOfTwo(int in)
+{
+	int out;
+
+	for (out = 1; out < in; out <<= 1)
+		;
+
+	return out;
+}
+
+unsigned short FloatToHalf(float in)
+{
+	unsigned short out;
+	
+	union
+	{
+		float f;
+		unsigned int i;
+	} f32;
+
+	int sign, inExponent, inFraction;
+	int outExponent, outFraction;
+
+	f32.f = in;
+
+	sign       = (f32.i & 0x80000000) >> 31;
+	inExponent = (f32.i & 0x7F800000) >> 23;
+	inFraction =  f32.i & 0x007FFFFF;
+
+	outExponent = CLAMP(inExponent - 127, -15, 16) + 15;
+
+	outFraction = 0;
+	if (outExponent == 0x1F)
+	{
+		if (inExponent == 0xFF && inFraction != 0)
+			outFraction = 0x3FF;
+	}
+	else if (outExponent == 0x00)
+	{
+		if (inExponent == 0x00 && inFraction != 0)
+			outFraction = 0x3FF;
+	}
+	else
+		outFraction = inFraction >> 13;
+
+	out = (sign << 15) | (outExponent << 10) | outFraction;
+
+	return out;
+}

Added: trunk/code/rend2/tr_extramath.h
===================================================================
--- trunk/code/rend2/tr_extramath.h	                        (rev 0)
+++ trunk/code/rend2/tr_extramath.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,104 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01 at gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_extramath.h
+
+#ifndef __TR_EXTRAMATH_H__
+#define __TR_EXTRAMATH_H__
+
+typedef vec_t matrix_t[16];
+typedef int vec2i_t[2];
+typedef int vec3i_t[3];
+typedef int vec4i_t[4];
+
+void Matrix16Zero( matrix_t out );
+void Matrix16Identity( matrix_t out );
+void Matrix16Copy( const matrix_t in, matrix_t out );
+void Matrix16Multiply( const matrix_t in1, const matrix_t in2, matrix_t out );
+void Matrix16Transform( const matrix_t in1, const vec4_t in2, vec4_t out );
+qboolean Matrix16Compare(const matrix_t a, const matrix_t b);
+void Matrix16Dump( const matrix_t in );
+void Matrix16Translation( vec3_t vec, matrix_t out );
+void Matrix16Ortho( float left, float right, float bottom, float top, float znear, float zfar, matrix_t out );
+void Matrix16View(vec3_t axes[3], vec3_t origin, matrix_t out);
+void Matrix16SimpleInverse( const matrix_t in, matrix_t out);
+
+#define VectorCopy2(a,b)		((b)[0]=(a)[0],(b)[1]=(a)[1])
+
+#define VectorCopy4(a,b)		((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])
+#define VectorSet4(v,x,y,z,w)	((v)[0]=(x),(v)[1]=(y),(v)[2]=(z),(v)[3]=(w))
+#define DotProduct4(a,b)        ((a)[0]*(b)[0] + (a)[1]*(b)[1] + (a)[2]*(b)[2] + (a)[3]*(b)[3])
+#define VectorScale4(a,b,c)     ((c)[0]=(a)[0]*(b),(c)[1]=(a)[1]*(b),(c)[2]=(a)[2]*(b),(c)[3]=(a)[3]*(b))
+
+#define VectorCopy5(a,b)		((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3],(b)[4]=(a)[4])
+
+#define OffsetByteToFloat(a)    ((float)(a) * 1.0f/127.5f - 1.0f)
+#define FloatToOffsetByte(a)    (byte)(((a) + 1.0f) * 127.5f)
+#define ByteToFloat(a)          ((float)(a) * 1.0f/255.0f)
+#define FloatToByte(a)          (byte)((a) * 255.0f)
+
+#define RGBtosRGB(a)            (((a) < 0.0031308f) ? (12.92f * (a)) : (1.055f * pow((a), 0.41666f) - 0.055f))
+#define sRGBtoRGB(a)            (((a) <= 0.04045f)  ? ((a) / 12.92f) : (pow((((a) + 0.055f) / 1.055f), 2.4)) )
+
+static ID_INLINE int VectorCompare4(const vec4_t v1, const vec4_t v2)
+{
+	if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3])
+	{
+		return 0;
+	}
+	return 1;
+}
+
+static ID_INLINE int VectorCompare5(const vec5_t v1, const vec5_t v2)
+{
+	if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3] || v1[4] != v2[4])
+	{
+		return 0;
+	}
+	return 1;
+}
+
+void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c);
+
+
+qboolean SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2);
+void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3);
+
+#ifndef SGN
+#define SGN(x) (((x) >= 0) ? !!(x) : -1)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef CLAMP
+#define CLAMP(a,b,c) MIN(MAX((a),(b)),(c))
+#endif
+
+int NextPowerOfTwo(int in);
+unsigned short FloatToHalf(float in);
+
+#endif

Added: trunk/code/rend2/tr_extratypes.h
===================================================================
--- trunk/code/rend2/tr_extratypes.h	                        (rev 0)
+++ trunk/code/rend2/tr_extratypes.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,43 @@
+/*
+===========================================================================
+Copyright (C) 2009-2011 Andrei Drexler, Richard Allen, James Canete
+
+This file is part of Reaction source code.
+
+Reaction source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Reaction source code 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 Reaction source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#ifndef __TR_EXTRATYPES_H__
+#define __TR_EXTRATYPES_H__
+
+// tr_extratypes.h, for mods that want to extend tr_types.h without losing compatibility with original VMs
+
+// extra renderfx flags start at 0x0400
+#define RF_SUNFLARE			0x0400
+
+// extra refdef flags start at 0x0008
+#define RDF_NOFOG		0x0008		// don't apply fog
+#define RDF_EXTRA		0x0010		// Makro - refdefex_t to follow after refdef_t
+#define RDF_SUNLIGHT    0x0020      // SmileTheory - render sunlight and shadows
+
+typedef struct {
+	float			blurFactor;
+	float           sunDir[3];
+	float           sunCol[3];
+	float           sunAmbCol[3];
+} refdefex_t;
+
+#endif
\ No newline at end of file

Added: trunk/code/rend2/tr_fbo.c
===================================================================
--- trunk/code/rend2/tr_fbo.c	                        (rev 0)
+++ trunk/code/rend2/tr_fbo.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,843 @@
+/*
+===========================================================================
+Copyright (C) 2006 Kirk Barnes
+Copyright (C) 2006-2008 Robert Beckebans <trebor_7 at users.sourceforge.net>
+
+This file is part of XreaL source code.
+
+XreaL source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+XreaL source code 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 XreaL source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_fbo.c
+#include "tr_local.h"
+
+/*
+=============
+R_CheckFBO
+=============
+*/
+qboolean R_CheckFBO(const FBO_t * fbo)
+{
+	int             code;
+	int             id;
+
+	qglGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &id);
+	qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->frameBuffer);
+
+	code = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+
+	if(code == GL_FRAMEBUFFER_COMPLETE_EXT)
+	{
+		qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
+		return qtrue;
+	}
+
+	// an error occured
+	switch (code)
+	{
+		case GL_FRAMEBUFFER_COMPLETE_EXT:
+			break;
+
+		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Unsupported framebuffer format\n", fbo->name);
+			break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete attachment\n", fbo->name);
+			break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing attachment\n", fbo->name);
+			break;
+
+			//case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
+			//  ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, duplicate attachment\n", fbo->name);
+			//  break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same dimensions\n",
+					  fbo->name);
+			break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same format\n",
+					  fbo->name);
+			break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing draw buffer\n", fbo->name);
+			break;
+
+		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing read buffer\n", fbo->name);
+			break;
+
+		default:
+			ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) unknown error 0x%X\n", fbo->name, code);
+			//ri.Error(ERR_FATAL, "R_CheckFBO: (%s) unknown error 0x%X", fbo->name, code);
+			//assert(0);
+			break;
+	}
+
+	qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
+
+	return qfalse;
+}
+
+/*
+============
+FBO_Create
+============
+*/
+FBO_t          *FBO_Create(const char *name, int width, int height)
+{
+	FBO_t          *fbo;
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "FBO_Create: \"%s\" is too long\n", name);
+	}
+
+	if(width <= 0 || width > glRefConfig.maxRenderbufferSize)
+	{
+		ri.Error(ERR_DROP, "FBO_Create: bad width %i", width);
+	}
+
+	if(height <= 0 || height > glRefConfig.maxRenderbufferSize)
+	{
+		ri.Error(ERR_DROP, "FBO_Create: bad height %i", height);
+	}
+
+	if(tr.numFBOs == MAX_FBOS)
+	{
+		ri.Error(ERR_DROP, "FBO_Create: MAX_FBOS hit");
+	}
+
+	fbo = tr.fbos[tr.numFBOs] = ri.Hunk_Alloc(sizeof(*fbo), h_low);
+	Q_strncpyz(fbo->name, name, sizeof(fbo->name));
+	fbo->index = tr.numFBOs++;
+	fbo->width = width;
+	fbo->height = height;
+
+	qglGenFramebuffersEXT(1, &fbo->frameBuffer);
+
+	return fbo;
+}
+
+void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample)
+{
+	uint32_t *pRenderBuffer;
+	GLenum attachment;
+	qboolean absent;
+
+	switch(format)
+	{
+		case GL_RGB:
+		case GL_RGBA:
+		case GL_RGB8:
+		case GL_RGBA8:
+		case GL_RGB16F_ARB:
+		case GL_RGBA16F_ARB:
+		case GL_RGB32F_ARB:
+		case GL_RGBA32F_ARB:
+			fbo->colorFormat = format;
+			pRenderBuffer = &fbo->colorBuffers[index];
+			attachment = GL_COLOR_ATTACHMENT0_EXT + index;
+			break;
+
+		case GL_DEPTH_COMPONENT:
+		case GL_DEPTH_COMPONENT16_ARB:
+		case GL_DEPTH_COMPONENT24_ARB:
+		case GL_DEPTH_COMPONENT32_ARB:
+			fbo->depthFormat = format;
+			pRenderBuffer = &fbo->depthBuffer;
+			attachment = GL_DEPTH_ATTACHMENT_EXT;
+			break;
+
+		case GL_STENCIL_INDEX:
+		case GL_STENCIL_INDEX1_EXT:
+		case GL_STENCIL_INDEX4_EXT:
+		case GL_STENCIL_INDEX8_EXT:
+		case GL_STENCIL_INDEX16_EXT:
+			fbo->stencilFormat = format;
+			pRenderBuffer = &fbo->stencilBuffer;
+			attachment = GL_STENCIL_ATTACHMENT_EXT;
+			break;
+
+		case GL_DEPTH_STENCIL_EXT:
+		case GL_DEPTH24_STENCIL8_EXT:
+			fbo->packedDepthStencilFormat = format;
+			pRenderBuffer = &fbo->packedDepthStencilBuffer;
+			attachment = 0; // special for stencil and depth
+			break;
+
+		default:
+			ri.Printf(PRINT_WARNING, "FBO_CreateBuffer: invalid format %d\n", format);
+			return;
+	}
+
+	absent = *pRenderBuffer == 0;
+	if (absent)
+		qglGenRenderbuffersEXT(1, pRenderBuffer);
+
+	qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, *pRenderBuffer);
+	if (multisample && glRefConfig.framebufferMultisample)
+	{
+		qglRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, multisample, format, fbo->width, fbo->height);
+	}
+	else
+	{
+		qglRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, format, fbo->width, fbo->height);
+	}
+
+	if(absent)
+	{
+		if (attachment == 0)
+		{
+			qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,   GL_RENDERBUFFER_EXT, *pRenderBuffer);
+			qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, *pRenderBuffer);
+		}
+		else
+			qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, attachment, GL_RENDERBUFFER_EXT, *pRenderBuffer);
+	}
+}
+
+
+/*
+=================
+R_AttachFBOTexture1D
+=================
+*/
+void R_AttachFBOTexture1D(int texId, int index)
+{
+	if(index < 0 || index >= glRefConfig.maxColorAttachments)
+	{
+		ri.Printf(PRINT_WARNING, "R_AttachFBOTexture1D: invalid attachment index %i\n", index);
+		return;
+	}
+
+	qglFramebufferTexture1DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_TEXTURE_1D, texId, 0);
+}
+
+/*
+=================
+R_AttachFBOTexture2D
+=================
+*/
+void R_AttachFBOTexture2D(int target, int texId, int index)
+{
+	if(target != GL_TEXTURE_2D && (target < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB))
+	{
+		ri.Printf(PRINT_WARNING, "R_AttachFBOTexture2D: invalid target %i\n", target);
+		return;
+	}
+
+	if(index < 0 || index >= glRefConfig.maxColorAttachments)
+	{
+		ri.Printf(PRINT_WARNING, "R_AttachFBOTexture2D: invalid attachment index %i\n", index);
+		return;
+	}
+
+	qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, target, texId, 0);
+}
+
+/*
+=================
+R_AttachFBOTexture3D
+=================
+*/
+void R_AttachFBOTexture3D(int texId, int index, int zOffset)
+{
+	if(index < 0 || index >= glRefConfig.maxColorAttachments)
+	{
+		ri.Printf(PRINT_WARNING, "R_AttachFBOTexture3D: invalid attachment index %i\n", index);
+		return;
+	}
+
+	qglFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_TEXTURE_3D_EXT, texId, 0, zOffset);
+}
+
+/*
+=================
+R_AttachFBOTextureDepth
+=================
+*/
+void R_AttachFBOTextureDepth(int texId)
+{
+	qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0);
+}
+
+/*
+=================
+R_AttachFBOTexturePackedDepthStencil
+=================
+*/
+void R_AttachFBOTexturePackedDepthStencil(int texId)
+{
+	qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0);
+	qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0);
+}
+
+void FBO_AttachTextureImage(image_t *img, int index)
+{
+	if (!glState.currentFBO)
+	{
+		ri.Printf(PRINT_WARNING, "FBO: attempted to attach a texture image with no FBO bound!\n");
+		return;
+	}
+
+	R_AttachFBOTexture2D(GL_TEXTURE_2D, img->texnum, index);
+	glState.currentFBO->colorImage[index] = img;
+}
+
+/*
+============
+FBO_Bind
+============
+*/
+void FBO_Bind(FBO_t * fbo)
+{
+	if (fbo && glState.currentFBO == fbo)
+		return;
+		
+	if (r_logFile->integer)
+	{
+		// don't just call LogComment, or we will get a call to va() every frame!
+		if (fbo)
+			GLimp_LogComment(va("--- FBO_Bind( %s ) ---\n", fbo->name));
+		else
+			GLimp_LogComment("--- FBO_Bind ( NULL ) ---\n");
+	}
+
+	if (!fbo)
+	{
+		qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+		//qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
+		glState.currentFBO = NULL;
+		
+		return;
+	}
+		
+	qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->frameBuffer);
+
+	/*
+	   if(fbo->colorBuffers[0])
+	   {
+	   qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->colorBuffers[0]);
+	   }
+	 */
+
+	/*
+	   if(fbo->depthBuffer)
+	   {
+	   qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->depthBuffer);
+	   qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthBuffer);
+	   }
+	 */
+
+	glState.currentFBO = fbo;
+}
+
+/*
+============
+FBO_Init
+============
+*/
+void FBO_Init(void)
+{
+	int             i;
+	// int             width, height, hdrFormat, multisample;
+	int             hdrFormat, multisample;
+
+	ri.Printf(PRINT_ALL, "------- FBO_Init -------\n");
+
+	if(!glRefConfig.framebufferObject)
+		return;
+
+	tr.numFBOs = 0;
+
+	GL_CheckErrors();
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+/*	if(glRefConfig.textureNonPowerOfTwo)
+	{
+		width = glConfig.vidWidth;
+		height = glConfig.vidHeight;
+	}
+	else
+	{
+		width = NextPowerOfTwo(glConfig.vidWidth);
+		height = NextPowerOfTwo(glConfig.vidHeight);
+	} */
+
+	hdrFormat = GL_RGBA8;
+	if (r_hdr->integer && glRefConfig.framebufferObject && glRefConfig.textureFloat)
+	{
+		hdrFormat = GL_RGB16F_ARB;
+	}
+
+	qglGetIntegerv(GL_MAX_SAMPLES_EXT, &multisample);
+
+	if (r_ext_framebuffer_multisample->integer < multisample)
+	{
+		multisample = r_ext_framebuffer_multisample->integer;
+	}
+
+	if (multisample < 2 || !glRefConfig.framebufferBlit)
+		multisample = 0;
+
+	if (multisample != r_ext_framebuffer_multisample->integer)
+	{
+		ri.Cvar_SetValue("r_ext_framebuffer_multisample", (float)multisample);
+	}
+	
+	if (multisample && glRefConfig.framebufferMultisample)
+	{
+		tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height);
+		FBO_Bind(tr.renderFbo);
+
+		FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample);
+		FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample);
+
+		R_CheckFBO(tr.renderFbo);
+
+
+		tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height);
+		FBO_Bind(tr.msaaResolveFbo);
+
+		//FBO_CreateBuffer(tr.msaaResolveFbo, hdrFormat, 0, 0);
+		FBO_AttachTextureImage(tr.renderImage, 0);
+
+		//FBO_CreateBuffer(tr.msaaResolveFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0);
+		R_AttachFBOTextureDepth(tr.renderDepthImage->texnum);
+
+		R_CheckFBO(tr.msaaResolveFbo);
+	}
+	else
+	{
+		tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height);
+		FBO_Bind(tr.renderFbo);
+
+		//FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, 0);
+		FBO_AttachTextureImage(tr.renderImage, 0);
+
+		//FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0);
+		R_AttachFBOTextureDepth(tr.renderDepthImage->texnum);
+
+		R_CheckFBO(tr.renderFbo);
+	}
+
+	// clear render buffer
+	// this fixes the corrupt screen bug with r_hdr 1 on older hardware
+	FBO_Bind(tr.renderFbo);
+	qglClearColor( 1, 0, 0.5, 1 );
+	qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+	FBO_Bind(NULL);
+
+#ifdef REACTION
+	{
+		tr.godRaysFbo = FBO_Create("_godRays", tr.renderDepthImage->width, tr.renderDepthImage->height);
+		FBO_Bind(tr.godRaysFbo);
+
+		//FBO_CreateBuffer(tr.godRaysFbo, GL_RGBA8, 0, multisample);
+		FBO_AttachTextureImage(tr.godRaysImage, 0);
+
+		//FBO_CreateBuffer(tr.godRaysFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample);
+		R_AttachFBOTextureDepth(tr.renderDepthImage->texnum);
+
+		R_CheckFBO(tr.godRaysFbo);
+	}
+#endif
+
+	// FIXME: Don't use separate color/depth buffers for a shadow buffer
+	for( i = 0; i < MAX_DRAWN_PSHADOWS; i++)
+	{
+		tr.pshadowFbos[i] = FBO_Create(va("_shadowmap%d", i), tr.pshadowMaps[i]->width, tr.pshadowMaps[i]->height);
+		FBO_Bind(tr.pshadowFbos[i]);
+
+		//FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0);
+		FBO_AttachTextureImage(tr.pshadowMaps[i], 0);
+
+		FBO_CreateBuffer(tr.pshadowFbos[i], GL_DEPTH_COMPONENT24_ARB, 0, 0);
+		//R_AttachFBOTextureDepth(tr.textureDepthImage->texnum);
+
+		R_CheckFBO(tr.pshadowFbos[i]);
+	}
+
+	for ( i = 0; i < 3; i++)
+	{
+		tr.sunShadowFbo[i] = FBO_Create("_sunshadowmap", tr.sunShadowDepthImage[i]->width, tr.sunShadowDepthImage[i]->height);
+		FBO_Bind(tr.sunShadowFbo[i]);
+
+		//FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0);
+		//FBO_AttachTextureImage(tr.sunShadowImage, 0);
+		qglDrawBuffer(GL_NONE);
+		qglReadBuffer(GL_NONE);
+
+		//FBO_CreateBuffer(tr.sunShadowFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0);
+		R_AttachFBOTextureDepth(tr.sunShadowDepthImage[i]->texnum);
+
+		R_CheckFBO(tr.sunShadowFbo[i]);
+	}
+
+	for (i = 0; i < 2; i++)
+	{
+		tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height);
+		FBO_Bind(tr.textureScratchFbo[i]);
+
+		//FBO_CreateBuffer(tr.textureScratchFbo[i], GL_RGBA8, 0, 0);
+		FBO_AttachTextureImage(tr.textureScratchImage[i], 0);
+
+		R_CheckFBO(tr.textureScratchFbo[i]);
+	}
+
+	{
+		tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height);
+		FBO_Bind(tr.calcLevelsFbo);
+
+		//FBO_CreateBuffer(tr.calcLevelsFbo, hdrFormat, 0, 0);
+		FBO_AttachTextureImage(tr.calcLevelsImage, 0);
+
+		R_CheckFBO(tr.calcLevelsFbo);
+	}
+
+	{
+		tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height);
+		FBO_Bind(tr.targetLevelsFbo);
+
+		//FBO_CreateBuffer(tr.targetLevelsFbo, hdrFormat, 0, 0);
+		FBO_AttachTextureImage(tr.targetLevelsImage, 0);
+
+		R_CheckFBO(tr.targetLevelsFbo);
+	}
+
+	{
+		//tr.screenScratchFbo = FBO_Create("_screenscratch", width, height);
+		tr.screenScratchFbo = FBO_Create("_screenscratch", tr.screenScratchImage->width, tr.screenScratchImage->height);
+		FBO_Bind(tr.screenScratchFbo);
+		
+		//FBO_CreateBuffer(tr.screenScratchFbo, format, 0, 0);
+		FBO_AttachTextureImage(tr.screenScratchImage, 0);
+
+		// FIXME: hack: share zbuffer between render fbo and pre-screen fbo
+		//FBO_CreateBuffer(tr.screenScratchFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0);
+		R_AttachFBOTextureDepth(tr.renderDepthImage->texnum);
+
+		R_CheckFBO(tr.screenScratchFbo);
+	}
+
+	for (i = 0; i < 2; i++)
+	{
+		tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height);
+		FBO_Bind(tr.quarterFbo[i]);
+
+		//FBO_CreateBuffer(tr.quarterFbo[i], hdrFormat, 0, 0);
+		FBO_AttachTextureImage(tr.quarterImage[i], 0);
+
+		R_CheckFBO(tr.quarterFbo[i]);
+	}
+
+	{
+		tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height);
+		FBO_Bind(tr.screenShadowFbo);
+		
+		FBO_AttachTextureImage(tr.screenShadowImage, 0);
+
+		R_CheckFBO(tr.screenShadowFbo);
+	}
+
+	if (r_ssao->integer)
+	{
+		tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height);
+		FBO_Bind(tr.hdrDepthFbo);
+
+		FBO_AttachTextureImage(tr.hdrDepthImage, 0);
+
+		R_CheckFBO(tr.hdrDepthFbo);
+
+		tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height);
+		FBO_Bind(tr.screenSsaoFbo);
+		
+		FBO_AttachTextureImage(tr.screenSsaoImage, 0);
+
+		R_CheckFBO(tr.screenSsaoFbo);
+	}
+
+	GL_CheckErrors();
+
+	FBO_Bind(NULL);
+}
+
+/*
+============
+FBO_Shutdown
+============
+*/
+void FBO_Shutdown(void)
+{
+	int             i, j;
+	FBO_t          *fbo;
+
+	ri.Printf(PRINT_ALL, "------- FBO_Shutdown -------\n");
+
+	if(!glRefConfig.framebufferObject)
+		return;
+
+	FBO_Bind(NULL);
+
+	for(i = 0; i < tr.numFBOs; i++)
+	{
+		fbo = tr.fbos[i];
+
+		for(j = 0; j < glRefConfig.maxColorAttachments; j++)
+		{
+			if(fbo->colorBuffers[j])
+				qglDeleteRenderbuffersEXT(1, &fbo->colorBuffers[j]);
+		}
+
+		if(fbo->depthBuffer)
+			qglDeleteRenderbuffersEXT(1, &fbo->depthBuffer);
+
+		if(fbo->stencilBuffer)
+			qglDeleteRenderbuffersEXT(1, &fbo->stencilBuffer);
+
+		if(fbo->frameBuffer)
+			qglDeleteFramebuffersEXT(1, &fbo->frameBuffer);
+	}
+}
+
+/*
+============
+R_FBOList_f
+============
+*/
+void R_FBOList_f(void)
+{
+	int             i;
+	FBO_t          *fbo;
+
+	if(!glRefConfig.framebufferObject)
+	{
+		ri.Printf(PRINT_ALL, "GL_EXT_framebuffer_object is not available.\n");
+		return;
+	}
+
+	ri.Printf(PRINT_ALL, "             size       name\n");
+	ri.Printf(PRINT_ALL, "----------------------------------------------------------\n");
+
+	for(i = 0; i < tr.numFBOs; i++)
+	{
+		fbo = tr.fbos[i];
+
+		ri.Printf(PRINT_ALL, "  %4i: %4i %4i %s\n", i, fbo->width, fbo->height, fbo->name);
+	}
+
+	ri.Printf(PRINT_ALL, " %i FBOs\n", tr.numFBOs);
+}
+
+// FIXME
+extern void RB_SetGL2D (void);
+
+void FBO_BlitFromTexture(struct image_s *src, vec4i_t inSrcBox, vec2_t inSrcTexScale, FBO_t *dst, vec4i_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend)
+{
+	vec4i_t dstBox, srcBox;
+	vec2_t srcTexScale;
+	vec4_t color;
+	vec4_t quadVerts[4];
+	vec2_t texCoords[4];
+	vec2_t invTexRes;
+	FBO_t *oldFbo = glState.currentFBO;
+
+	if (!src)
+		return;
+
+	if (inSrcBox)
+	{
+		VectorSet4(srcBox, inSrcBox[0], inSrcBox[1], inSrcBox[0] + inSrcBox[2],  inSrcBox[1] + inSrcBox[3]);
+	}
+	else
+	{
+		VectorSet4(srcBox, 0, 0, src->width, src->height);
+	}
+
+	// framebuffers are 0 bottom, Y up.
+	if (inDstBox)
+	{
+		if (dst)
+		{
+			dstBox[0] = inDstBox[0];
+			dstBox[1] = dst->height - inDstBox[1] - inDstBox[3];
+			dstBox[2] = inDstBox[0] + inDstBox[2];
+			dstBox[3] = dst->height - inDstBox[1];
+		}
+		else
+		{
+			dstBox[0] = inDstBox[0];
+			dstBox[1] = glConfig.vidHeight - inDstBox[1] - inDstBox[3];
+			dstBox[2] = inDstBox[0] + inDstBox[2];
+			dstBox[3] = glConfig.vidHeight - inDstBox[1];
+		}
+	}
+	else if (dst)
+	{
+		VectorSet4(dstBox, 0, dst->height, dst->width, 0);
+	}
+	else
+	{
+		VectorSet4(dstBox, 0, glConfig.vidHeight, glConfig.vidWidth, 0);
+	}
+
+	if (inSrcTexScale)
+	{
+		VectorCopy2(inSrcTexScale, srcTexScale);
+	}
+	else
+	{
+		srcTexScale[0] = srcTexScale[1] = 1.0f;
+	}
+
+	if (inColor)
+	{
+		VectorCopy4(inColor, color);
+	}
+	else
+	{
+		color[0] = color[1] = color[2] = color[3] = 1.0f;
+	}
+
+	if (!shaderProgram)
+	{
+		shaderProgram = &tr.textureColorShader;
+	}
+
+	FBO_Bind(dst);
+
+	RB_SetGL2D();
+
+	GL_SelectTexture(TB_COLORMAP);
+
+	GL_Bind(src);
+
+	VectorSet4(quadVerts[0], dstBox[0], dstBox[1], 0, 1);
+	VectorSet4(quadVerts[1], dstBox[2], dstBox[1], 0, 1);
+	VectorSet4(quadVerts[2], dstBox[2], dstBox[3], 0, 1);
+	VectorSet4(quadVerts[3], dstBox[0], dstBox[3], 0, 1);
+
+	texCoords[0][0] = srcBox[0] / (float)src->width; texCoords[0][1] = 1.0f - srcBox[1] / (float)src->height;
+	texCoords[1][0] = srcBox[2] / (float)src->width; texCoords[1][1] = 1.0f - srcBox[1] / (float)src->height;
+	texCoords[2][0] = srcBox[2] / (float)src->width; texCoords[2][1] = 1.0f - srcBox[3] / (float)src->height;
+	texCoords[3][0] = srcBox[0] / (float)src->width; texCoords[3][1] = 1.0f - srcBox[3] / (float)src->height;
+
+	invTexRes[0] = 1.0f / src->width  * srcTexScale[0];
+	invTexRes[1] = 1.0f / src->height * srcTexScale[1];
+
+	GL_State( blend );
+
+	GLSL_BindProgram(shaderProgram);
+	
+	GLSL_SetUniformMatrix16(shaderProgram, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+	GLSL_SetUniformVec4(shaderProgram, TEXTURECOLOR_UNIFORM_COLOR, color);
+	GLSL_SetUniformVec2(shaderProgram, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes);
+	GLSL_SetUniformVec2(shaderProgram, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax);
+	GLSL_SetUniformVec3(shaderProgram, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear);
+
+	RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+
+	FBO_Bind(oldFbo);
+}
+
+void FBO_Blit(FBO_t *src, vec4i_t inSrcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend)
+{
+	vec4i_t srcBox;
+
+	if (!src)
+		return;
+
+	// framebuffers are 0 bottom, Y up.
+	if (inSrcBox)
+	{
+		srcBox[0] = inSrcBox[0];
+		srcBox[1] = src->height - inSrcBox[1] - inSrcBox[3];
+		srcBox[2] = inSrcBox[2];
+		srcBox[3] = inSrcBox[3];
+	}
+	else
+	{
+		VectorSet4(srcBox, 0, src->height, src->width, -src->height);
+	}
+
+	FBO_BlitFromTexture(src->colorImage[0], srcBox, srcTexScale, dst, dstBox, shaderProgram, color, blend | GLS_DEPTHTEST_DISABLE);
+}
+
+void FBO_FastBlit(FBO_t *src, vec4i_t srcBox, FBO_t *dst, vec4i_t dstBox, int buffers, int filter)
+{
+	vec4i_t srcBoxFinal, dstBoxFinal;
+	GLuint srcFb, dstFb;
+
+	if (!glRefConfig.framebufferBlit)
+	{
+		FBO_Blit(src, srcBox, NULL, dst, dstBox, NULL, NULL, 0);
+		return;
+	}
+
+	// get to a neutral state first
+	FBO_Bind(NULL);
+
+	srcFb = src ? src->frameBuffer : 0;
+	dstFb = dst ? dst->frameBuffer : 0;
+
+	if (!srcBox)
+	{
+		if (src)
+		{
+			VectorSet4(srcBoxFinal, 0, 0, src->width, src->height);
+		}
+		else
+		{
+			VectorSet4(srcBoxFinal, 0, 0, glConfig.vidWidth, glConfig.vidHeight);
+		}
+	}
+	else
+	{
+		VectorSet4(srcBoxFinal, srcBox[0], srcBox[1], srcBox[0] + srcBox[2], srcBox[1] + srcBox[3]);
+	}
+
+	if (!dstBox)
+	{
+		if (dst)
+		{
+			VectorSet4(dstBoxFinal, 0, 0, dst->width, dst->height);
+		}
+		else
+		{
+			VectorSet4(dstBoxFinal, 0, 0, glConfig.vidWidth, glConfig.vidHeight);
+		}
+	}
+	else
+	{
+		VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]);
+	}
+
+	qglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, srcFb);
+	qglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dstFb);
+	qglBlitFramebufferEXT(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3],
+	                      dstBoxFinal[0], dstBoxFinal[1], dstBoxFinal[2], dstBoxFinal[3],
+						  buffers, filter);
+
+	qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+	glState.currentFBO = NULL;
+}
\ No newline at end of file

Added: trunk/code/rend2/tr_fbo.h
===================================================================
--- trunk/code/rend2/tr_fbo.h	                        (rev 0)
+++ trunk/code/rend2/tr_fbo.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,64 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01 at gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_fbo.h
+
+#ifndef __TR_FBO_H__
+#define __TR_FBO_H__
+
+struct image_s;
+struct shaderProgram_s;
+
+typedef struct FBO_s
+{
+	char            name[MAX_QPATH];
+
+	int             index;
+
+	uint32_t        frameBuffer;
+
+	uint32_t        colorBuffers[16];
+	int             colorFormat;
+	struct image_s  *colorImage[16];
+
+	uint32_t        depthBuffer;
+	int             depthFormat;
+
+	uint32_t        stencilBuffer;
+	int             stencilFormat;
+
+	uint32_t        packedDepthStencilBuffer;
+	int             packedDepthStencilFormat;
+
+	int             width;
+	int             height;
+} FBO_t;
+
+void FBO_Bind(FBO_t *fbo);
+void FBO_Init(void);
+void FBO_Shutdown(void);
+
+void FBO_BlitFromTexture(struct image_s *src, vec4i_t srcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend);
+void FBO_Blit(FBO_t *src, vec4i_t srcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend);
+void FBO_FastBlit(FBO_t *src, vec4i_t srcBox, FBO_t *dst, vec4i_t dstBox, int buffers, int filter);
+
+
+#endif

Added: trunk/code/rend2/tr_flares.c
===================================================================
--- trunk/code/rend2/tr_flares.c	                        (rev 0)
+++ trunk/code/rend2/tr_flares.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,532 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_flares.c
+
+#include "tr_local.h"
+
+/*
+=============================================================================
+
+LIGHT FLARES
+
+A light flare is an effect that takes place inside the eye when bright light
+sources are visible.  The size of the flare reletive to the screen is nearly
+constant, irrespective of distance, but the intensity should be proportional to the
+projected area of the light source.
+
+A surface that has been flagged as having a light flare will calculate the depth
+buffer value that its midpoint should have when the surface is added.
+
+After all opaque surfaces have been rendered, the depth buffer is read back for
+each flare in view.  If the point has not been obscured by a closer surface, the
+flare should be drawn.
+
+Surfaces that have a repeated texture should never be flagged as flaring, because
+there will only be a single flare added at the midpoint of the polygon.
+
+To prevent abrupt popping, the intensity of the flare is interpolated up and
+down as it changes visibility.  This involves scene to scene state, unlike almost
+all other aspects of the renderer, and is complicated by the fact that a single
+frame may have multiple scenes.
+
+RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially
+up to five or more times in a frame with 3D status bar icons).
+
+=============================================================================
+*/
+
+
+// flare states maintain visibility over multiple frames for fading
+// layers: view, mirror, menu
+typedef struct flare_s {
+	struct		flare_s	*next;		// for active chain
+
+	int			addedFrame;
+
+	qboolean	inPortal;				// true if in a portal view of the scene
+	int			frameSceneNum;
+	void		*surface;
+	int			fogNum;
+
+	int			fadeTime;
+
+	qboolean	visible;			// state of last test
+	float		drawIntensity;		// may be non 0 even if !visible due to fading
+
+	int			windowX, windowY;
+	float		eyeZ;
+
+	vec3_t		origin;
+	vec3_t		color;
+} flare_t;
+
+#define		MAX_FLARES		128
+
+flare_t		r_flareStructs[MAX_FLARES];
+flare_t		*r_activeFlares, *r_inactiveFlares;
+
+int flareCoeff;
+
+/*
+==================
+R_ClearFlares
+==================
+*/
+void R_ClearFlares( void ) {
+	int		i;
+
+	Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) );
+	r_activeFlares = NULL;
+	r_inactiveFlares = NULL;
+
+	for ( i = 0 ; i < MAX_FLARES ; i++ ) {
+		r_flareStructs[i].next = r_inactiveFlares;
+		r_inactiveFlares = &r_flareStructs[i];
+	}
+}
+
+
+/*
+==================
+RB_AddFlare
+
+This is called at surface tesselation time
+==================
+*/
+void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) {
+	int				i;
+	flare_t			*f;
+	vec3_t			local;
+	float			d = 1;
+	vec4_t			eye, clip, normalized, window;
+
+	backEnd.pc.c_flareAdds++;
+
+	if(normal && (normal[0] || normal[1] || normal[2]))
+	{
+		VectorSubtract( backEnd.viewParms.or.origin, point, local );
+		VectorNormalizeFast(local);
+		d = DotProduct(local, normal);
+
+		// If the viewer is behind the flare don't add it.
+		if(d < 0)
+			return;
+	}
+
+	// if the point is off the screen, don't bother adding it
+	// calculate screen coordinates and depth
+	R_TransformModelToClip( point, backEnd.or.modelMatrix, 
+		backEnd.viewParms.projectionMatrix, eye, clip );
+
+	// check to see if the point is completely off screen
+	for ( i = 0 ; i < 3 ; i++ ) {
+		if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) {
+			return;
+		}
+	}
+
+	R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window );
+
+	if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth
+		|| window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) {
+		return;	// shouldn't happen, since we check the clip[] above, except for FP rounding
+	}
+
+	// see if a flare with a matching surface, scene, and view exists
+	for ( f = r_activeFlares ; f ; f = f->next ) {
+		if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum
+			&& f->inPortal == backEnd.viewParms.isPortal ) {
+			break;
+		}
+	}
+
+	// allocate a new one
+	if (!f ) {
+		if ( !r_inactiveFlares ) {
+			// the list is completely full
+			return;
+		}
+		f = r_inactiveFlares;
+		r_inactiveFlares = r_inactiveFlares->next;
+		f->next = r_activeFlares;
+		r_activeFlares = f;
+
+		f->surface = surface;
+		f->frameSceneNum = backEnd.viewParms.frameSceneNum;
+		f->inPortal = backEnd.viewParms.isPortal;
+		f->addedFrame = -1;
+	}
+
+	if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) {
+		f->visible = qfalse;
+		f->fadeTime = backEnd.refdef.time - 2000;
+	}
+
+	f->addedFrame = backEnd.viewParms.frameCount;
+	f->fogNum = fogNum;
+
+	VectorCopy(point, f->origin);
+	VectorCopy( color, f->color );
+
+	// fade the intensity of the flare down as the
+	// light surface turns away from the viewer
+	VectorScale( f->color, d, f->color ); 
+
+	// save info needed to test
+	f->windowX = backEnd.viewParms.viewportX + window[0];
+	f->windowY = backEnd.viewParms.viewportY + window[1];
+
+	f->eyeZ = eye[2];
+}
+
+/*
+==================
+RB_AddDlightFlares
+==================
+*/
+void RB_AddDlightFlares( void ) {
+	dlight_t		*l;
+	int				i, j, k;
+	fog_t			*fog = NULL;
+
+	if ( !r_flares->integer ) {
+		return;
+	}
+
+	l = backEnd.refdef.dlights;
+
+	if(tr.world)
+		fog = tr.world->fogs;
+
+	for (i=0 ; i<backEnd.refdef.num_dlights ; i++, l++) {
+
+		if(fog)
+		{
+			// find which fog volume the light is in 
+			for ( j = 1 ; j < tr.world->numfogs ; j++ ) {
+				fog = &tr.world->fogs[j];
+				for ( k = 0 ; k < 3 ; k++ ) {
+					if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) {
+						break;
+					}
+				}
+				if ( k == 3 ) {
+					break;
+				}
+			}
+			if ( j == tr.world->numfogs ) {
+				j = 0;
+			}
+		}
+		else
+			j = 0;
+
+		RB_AddFlare( (void *)l, j, l->origin, l->color, NULL );
+	}
+}
+
+/*
+===============================================================================
+
+FLARE BACK END
+
+===============================================================================
+*/
+
+/*
+==================
+RB_TestFlare
+==================
+*/
+void RB_TestFlare( flare_t *f ) {
+	float			depth;
+	qboolean		visible;
+	float			fade;
+	float			screenZ;
+
+	backEnd.pc.c_flareTests++;
+
+	// doing a readpixels is as good as doing a glFinish(), so
+	// don't bother with another sync
+	glState.finishCalled = qfalse;
+
+	// read back the z buffer contents
+	qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
+
+	screenZ = backEnd.viewParms.projectionMatrix[14] / 
+		( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] );
+
+	visible = ( -f->eyeZ - -screenZ ) < 24;
+
+	if ( visible ) {
+		if ( !f->visible ) {
+			f->visible = qtrue;
+			f->fadeTime = backEnd.refdef.time - 1;
+		}
+		fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
+	} else {
+		if ( f->visible ) {
+			f->visible = qfalse;
+			f->fadeTime = backEnd.refdef.time - 1;
+		}
+		fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value;
+	}
+
+	if ( fade < 0 ) {
+		fade = 0;
+	}
+	if ( fade > 1 ) {
+		fade = 1;
+	}
+
+	f->drawIntensity = fade;
+}
+
+
+/*
+==================
+RB_RenderFlare
+==================
+*/
+void RB_RenderFlare( flare_t *f ) {
+	float			size;
+	vec3_t			color;
+	int				iColor[3];
+	float distance, intensity, factor;
+	byte fogFactors[3] = {255, 255, 255};
+
+	backEnd.pc.c_flareRenders++;
+
+	// We don't want too big values anyways when dividing by distance.
+	if(f->eyeZ > -1.0f)
+		distance = 1.0f;
+	else
+		distance = -f->eyeZ;
+
+	// calculate the flare size..
+	size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance );
+
+/*
+ * This is an alternative to intensity scaling. It changes the size of the flare on screen instead
+ * with growing distance. See in the description at the top why this is not the way to go.
+	// size will change ~ 1/r.
+	size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f));
+*/
+
+/*
+ * As flare sizes stay nearly constant with increasing distance we must decrease the intensity
+ * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be
+ * got by considering the ratio of
+ * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare)
+ * An important requirement is:
+ * intensity <= 1 for all distances.
+ *
+ * The formula used here to compute the intensity is as follows:
+ * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2
+ * As you can see, the intensity will have a max. of 1 when the distance is 0.
+ * The coefficient flareCoeff will determine the falloff speed with increasing distance.
+ */
+
+	factor = distance + size * sqrt(flareCoeff);
+	
+	intensity = flareCoeff * size * size / (factor * factor);
+
+	VectorScale(f->color, f->drawIntensity * intensity, color);
+
+// Calculations for fogging
+	if(tr.world && f->fogNum < tr.world->numfogs)
+	{
+		tess.numVertexes = 1;
+		VectorCopy(f->origin, tess.xyz[0]);
+		tess.fogNum = f->fogNum;
+	
+		RB_CalcModulateColorsByFog(fogFactors);
+		
+		// We don't need to render the flare if colors are 0 anyways.
+		if(!(fogFactors[0] || fogFactors[1] || fogFactors[2]))
+			return;
+	}
+
+	iColor[0] = color[0] * fogFactors[0];
+	iColor[1] = color[1] * fogFactors[1];
+	iColor[2] = color[2] * fogFactors[2];
+	
+	RB_BeginSurface( tr.flareShader, f->fogNum );
+
+	// FIXME: use quadstamp?
+	tess.xyz[tess.numVertexes][0] = f->windowX - size;
+	tess.xyz[tess.numVertexes][1] = f->windowY - size;
+	tess.texCoords[tess.numVertexes][0][0] = 0;
+	tess.texCoords[tess.numVertexes][0][1] = 0;
+	tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f;
+	tess.vertexColors[tess.numVertexes][3] = 1.0f;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = f->windowX - size;
+	tess.xyz[tess.numVertexes][1] = f->windowY + size;
+	tess.texCoords[tess.numVertexes][0][0] = 0;
+	tess.texCoords[tess.numVertexes][0][1] = 1;
+	tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f;
+	tess.vertexColors[tess.numVertexes][3] = 1.0f;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = f->windowX + size;
+	tess.xyz[tess.numVertexes][1] = f->windowY + size;
+	tess.texCoords[tess.numVertexes][0][0] = 1;
+	tess.texCoords[tess.numVertexes][0][1] = 1;
+	tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f;
+	tess.vertexColors[tess.numVertexes][3] = 1.0f;
+	tess.numVertexes++;
+
+	tess.xyz[tess.numVertexes][0] = f->windowX + size;
+	tess.xyz[tess.numVertexes][1] = f->windowY - size;
+	tess.texCoords[tess.numVertexes][0][0] = 1;
+	tess.texCoords[tess.numVertexes][0][1] = 0;
+	tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f;
+	tess.vertexColors[tess.numVertexes][3] = 1.0f;
+	tess.numVertexes++;
+
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 1;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 3;
+
+	RB_EndSurface();
+}
+
+/*
+==================
+RB_RenderFlares
+
+Because flares are simulating an occular effect, they should be drawn after
+everything (all views) in the entire frame has been drawn.
+
+Because of the way portals use the depth buffer to mark off areas, the
+needed information would be lost after each view, so we are forced to draw
+flares after each view.
+
+The resulting artifact is that flares in mirrors or portals don't dim properly
+when occluded by something in the main view, and portal flares that should
+extend past the portal edge will be overwritten.
+==================
+*/
+void RB_RenderFlares (void) {
+	flare_t		*f;
+	flare_t		**prev;
+	qboolean	draw;
+	matrix_t    oldmodelview, oldprojection, matrix;
+
+	if ( !r_flares->integer ) {
+		return;
+	}
+
+	if(r_flareCoeff->modified)
+	{
+		if(r_flareCoeff->value == 0.0f)
+			flareCoeff = atof(FLARE_STDCOEFF);
+		else
+			flareCoeff = r_flareCoeff->value;
+			
+		r_flareCoeff->modified = qfalse;
+	}
+
+	// Reset currentEntity to world so that any previously referenced entities
+	// don't have influence on the rendering of these flares (i.e. RF_ renderer flags).
+	backEnd.currentEntity = &tr.worldEntity;
+	backEnd.or = backEnd.viewParms.world;
+
+//	RB_AddDlightFlares();
+
+	// perform z buffer readback on each flare in this view
+	draw = qfalse;
+	prev = &r_activeFlares;
+	while ( ( f = *prev ) != NULL ) {
+		// throw out any flares that weren't added last frame
+		if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) {
+			*prev = f->next;
+			f->next = r_inactiveFlares;
+			r_inactiveFlares = f;
+			continue;
+		}
+
+		// don't draw any here that aren't from this scene / portal
+		f->drawIntensity = 0;
+		if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
+			&& f->inPortal == backEnd.viewParms.isPortal ) {
+			RB_TestFlare( f );
+			if ( f->drawIntensity ) {
+				draw = qtrue;
+			} else {
+				// this flare has completely faded out, so remove it from the chain
+				*prev = f->next;
+				f->next = r_inactiveFlares;
+				r_inactiveFlares = f;
+				continue;
+			}
+		}
+
+		prev = &f->next;
+	}
+
+	if ( !draw ) {
+		return;		// none visible
+	}
+
+	if ( backEnd.viewParms.isPortal ) {
+		qglDisable (GL_CLIP_PLANE0);
+	}
+
+	Matrix16Copy(glState.projection, oldprojection);
+	Matrix16Copy(glState.modelview, oldmodelview);
+	Matrix16Identity(matrix);
+	GL_SetModelviewMatrix(matrix);
+	Matrix16Ortho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth,
+	               backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight,
+	               -99999, 99999, matrix );
+	GL_SetProjectionMatrix(matrix);
+
+	for ( f = r_activeFlares ; f ; f = f->next ) {
+		if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
+			&& f->inPortal == backEnd.viewParms.isPortal
+			&& f->drawIntensity ) {
+			RB_RenderFlare( f );
+		}
+	}
+
+	GL_SetProjectionMatrix(oldprojection);
+	GL_SetModelviewMatrix(oldmodelview);
+}
+
+
+
+
+

Added: trunk/code/rend2/tr_font.c
===================================================================
--- trunk/code/rend2/tr_font.c	                        (rev 0)
+++ trunk/code/rend2/tr_font.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,555 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_font.c
+// 
+//
+// The font system uses FreeType 2.x to render TrueType fonts for use within the game.
+// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and 
+// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old 
+// fonts since the code is shared with standard Q3A.
+//
+// If you include this font rendering code in a commercial product you MUST include the
+// following somewhere with your product, see www.freetype.org for specifics or changes.
+// The Freetype code also uses some hinting techniques that MIGHT infringe on patents 
+// held by apple so be aware of that also.
+//
+// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code
+// disabled. This removes any potential patent issues and it keeps us from having to 
+// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require
+// an act of god to accomplish. 
+//
+// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType
+// credit in the credits ) and then saved off the glyph data and then hand touched up the 
+// font bitmaps so they scale a bit better in GL.
+//
+// There are limitations in the way fonts are saved and reloaded in that it is based on 
+// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point
+// you will end up with a single 18 point data file and image set. Typically you will want to 
+// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system
+// 
+// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we
+// use three or four scales, most of them exactly equaling the specific rendered size. We 
+// rendered three sizes in Team Arena, 12, 16, and 20. 
+//
+// To generate new font data you need to go through the following steps.
+// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path.
+// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and 
+//    point size. the original TrueType fonts must exist in fonts at this point.
+// 3. run the game, you should see things normally.
+// 4. Exit the game and there will be three dat files and at least three tga files. The 
+//    tga's are in 256x256 pages so if it takes three images to render a 24 point font you 
+//    will end up with fontImage_0_24.tga through fontImage_2_24.tga
+// 5. In future runs of the game, the system looks for these images and data files when a s
+//    specific point sized font is rendered and loads them for use. 
+// 6. Because of the original beta nature of the FreeType code you will probably want to hand
+//    touch the font bitmaps.
+// 
+// Currently a define in the project turns on or off the FreeType code which is currently 
+// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and 
+// uncheck the exclude from build check box in the FreeType2 area of the Renderer project. 
+
+
+#include "tr_local.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef BUILD_FREETYPE
+#include <ft2build.h>
+#include FT_ERRORS_H
+#include FT_SYSTEM_H
+#include FT_IMAGE_H
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+
+#define _FLOOR(x)  ((x) & -64)
+#define _CEIL(x)   (((x)+63) & -64)
+#define _TRUNC(x)  ((x) >> 6)
+
+FT_Library ftLibrary = NULL;  
+#endif
+
+#define MAX_FONTS 6
+static int registeredFontCount = 0;
+static fontInfo_t registeredFont[MAX_FONTS];
+
+#ifdef BUILD_FREETYPE
+void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) {
+	*left  = _FLOOR( glyph->metrics.horiBearingX );
+	*right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width );
+	*width = _TRUNC(*right - *left);
+
+	*top    = _CEIL( glyph->metrics.horiBearingY );
+	*bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height );
+	*height = _TRUNC( *top - *bottom );
+	*pitch  = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 );
+}
+
+
+FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) {
+	FT_Bitmap  *bit2;
+	int left, right, width, top, bottom, height, pitch, size;
+
+	R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch);
+
+	if ( glyph->format == ft_glyph_format_outline ) {
+		size   = pitch*height; 
+
+		bit2 = ri.Malloc(sizeof(FT_Bitmap));
+
+		bit2->width      = width;
+		bit2->rows       = height;
+		bit2->pitch      = pitch;
+		bit2->pixel_mode = ft_pixel_mode_grays;
+		//bit2->pixel_mode = ft_pixel_mode_mono;
+		bit2->buffer     = ri.Malloc(pitch*height);
+		bit2->num_grays = 256;
+
+		Com_Memset( bit2->buffer, 0, size );
+
+		FT_Outline_Translate( &glyph->outline, -left, -bottom );
+
+		FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 );
+
+		glyphOut->height = height;
+		glyphOut->pitch = pitch;
+		glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1;
+		glyphOut->bottom = bottom;
+
+		return bit2;
+	} else {
+		ri.Printf(PRINT_ALL, "Non-outline fonts are not supported\n");
+	}
+	return NULL;
+}
+
+void WriteTGA (char *filename, byte *data, int width, int height) {
+	byte			*buffer;
+	int				i, c;
+	int             row;
+	unsigned char  *flip;
+	unsigned char  *src, *dst;
+
+	buffer = ri.Malloc(width*height*4 + 18);
+	Com_Memset (buffer, 0, 18);
+	buffer[2] = 2;		// uncompressed type
+	buffer[12] = width&255;
+	buffer[13] = width>>8;
+	buffer[14] = height&255;
+	buffer[15] = height>>8;
+	buffer[16] = 32;	// pixel size
+
+	// swap rgb to bgr
+	c = 18 + width * height * 4;
+	for (i=18 ; i<c ; i+=4)
+	{
+		buffer[i] = data[i-18+2];		// blue
+		buffer[i+1] = data[i-18+1];		// green
+		buffer[i+2] = data[i-18+0];		// red
+		buffer[i+3] = data[i-18+3];		// alpha
+	}
+
+	// flip upside down
+	flip = (unsigned char *)ri.Malloc(width*4);
+	for(row = 0; row < height/2; row++)
+	{
+		src = buffer + 18 + row * 4 * width;
+		dst = buffer + 18 + (height - row - 1) * 4 * width;
+
+		Com_Memcpy(flip, src, width*4);
+		Com_Memcpy(src, dst, width*4);
+		Com_Memcpy(dst, flip, width*4);
+	}
+	ri.Free(flip);
+
+	ri.FS_WriteFile(filename, buffer, c);
+
+	//f = fopen (filename, "wb");
+	//fwrite (buffer, 1, c, f);
+	//fclose (f);
+
+	ri.Free (buffer);
+}
+
+static glyphInfo_t *RE_ConstructGlyphInfo(unsigned char *imageOut, int *xOut, int *yOut, int *maxHeight, FT_Face face, const unsigned char c, qboolean calcHeight) {
+	int i;
+	static glyphInfo_t glyph;
+	unsigned char *src, *dst;
+	float scaled_width, scaled_height;
+	FT_Bitmap *bitmap = NULL;
+
+	Com_Memset(&glyph, 0, sizeof(glyphInfo_t));
+	// make sure everything is here
+	if (face != NULL) {
+		FT_Load_Glyph(face, FT_Get_Char_Index( face, c), FT_LOAD_DEFAULT );
+		bitmap = R_RenderGlyph(face->glyph, &glyph);
+		if (bitmap) {
+			glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1;
+		} else {
+			return &glyph;
+		}
+
+		if (glyph.height > *maxHeight) {
+			*maxHeight = glyph.height;
+		}
+
+		if (calcHeight) {
+			ri.Free(bitmap->buffer);
+			ri.Free(bitmap);
+			return &glyph;
+		}
+
+/*
+		// need to convert to power of 2 sizes so we do not get 
+		// any scaling from the gl upload
+		for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1)
+			;
+		for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1)
+			;
+*/
+
+		scaled_width = glyph.pitch;
+		scaled_height = glyph.height;
+
+		// we need to make sure we fit
+		if (*xOut + scaled_width + 1 >= 255) {
+			*xOut = 0;
+			*yOut += *maxHeight + 1;
+		}
+
+		if (*yOut + *maxHeight + 1 >= 255) {
+			*yOut = -1;
+			*xOut = -1;
+			ri.Free(bitmap->buffer);
+			ri.Free(bitmap);
+			return &glyph;
+		}
+
+
+		src = bitmap->buffer;
+		dst = imageOut + (*yOut * 256) + *xOut;
+
+		if (bitmap->pixel_mode == ft_pixel_mode_mono) {
+			for (i = 0; i < glyph.height; i++) {
+				int j;
+				unsigned char *_src = src;
+				unsigned char *_dst = dst;
+				unsigned char mask = 0x80;
+				unsigned char val = *_src;
+				for (j = 0; j < glyph.pitch; j++) {
+					if (mask == 0x80) {
+						val = *_src++;
+					}
+					if (val & mask) {
+						*_dst = 0xff;
+					}
+					mask >>= 1;
+
+					if ( mask == 0 ) {
+						mask = 0x80;
+					}
+					_dst++;
+				}
+
+				src += glyph.pitch;
+				dst += 256;
+			}
+		} else {
+			for (i = 0; i < glyph.height; i++) {
+				Com_Memcpy(dst, src, glyph.pitch);
+				src += glyph.pitch;
+				dst += 256;
+			}
+		}
+
+		// we now have an 8 bit per pixel grey scale bitmap 
+		// that is width wide and pf->ftSize->metrics.y_ppem tall
+
+		glyph.imageHeight = scaled_height;
+		glyph.imageWidth = scaled_width;
+		glyph.s = (float)*xOut / 256;
+		glyph.t = (float)*yOut / 256;
+		glyph.s2 = glyph.s + (float)scaled_width / 256;
+		glyph.t2 = glyph.t + (float)scaled_height / 256;
+
+		*xOut += scaled_width + 1;
+
+		ri.Free(bitmap->buffer);
+		ri.Free(bitmap);
+	}
+
+	return &glyph;
+}
+#endif
+
+static int fdOffset;
+static byte	*fdFile;
+
+int readInt( void ) {
+	int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24);
+	fdOffset += 4;
+	return i;
+}
+
+typedef union {
+	byte	fred[4];
+	float	ffred;
+} poor;
+
+float readFloat( void ) {
+	poor	me;
+#if defined Q3_BIG_ENDIAN
+	me.fred[0] = fdFile[fdOffset+3];
+	me.fred[1] = fdFile[fdOffset+2];
+	me.fred[2] = fdFile[fdOffset+1];
+	me.fred[3] = fdFile[fdOffset+0];
+#elif defined Q3_LITTLE_ENDIAN
+	me.fred[0] = fdFile[fdOffset+0];
+	me.fred[1] = fdFile[fdOffset+1];
+	me.fred[2] = fdFile[fdOffset+2];
+	me.fred[3] = fdFile[fdOffset+3];
+#endif
+	fdOffset += 4;
+	return me.ffred;
+}
+
+void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) {
+#ifdef BUILD_FREETYPE
+	FT_Face face;
+	int j, k, xOut, yOut, lastStart, imageNumber;
+	int scaledSize, newSize, maxHeight, left;
+	unsigned char *out, *imageBuff;
+	glyphInfo_t *glyph;
+	image_t *image;
+	qhandle_t h;
+	float max;
+	float dpi = 72;
+	float glyphScale;
+#endif
+	void *faceData;
+	int i, len;
+	char name[1024];
+
+	if (!fontName) {
+		ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n");
+		return;
+	}
+
+	if (pointSize <= 0) {
+		pointSize = 12;
+	}
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	if (registeredFontCount >= MAX_FONTS) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: Too many fonts registered already.\n");
+		return;
+	}
+
+	Com_sprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize);
+	for (i = 0; i < registeredFontCount; i++) {
+		if (Q_stricmp(name, registeredFont[i].name) == 0) {
+			Com_Memcpy(font, &registeredFont[i], sizeof(fontInfo_t));
+			return;
+		}
+	}
+
+	len = ri.FS_ReadFile(name, NULL);
+	if (len == sizeof(fontInfo_t)) {
+		ri.FS_ReadFile(name, &faceData);
+		fdOffset = 0;
+		fdFile = faceData;
+		for(i=0; i<GLYPHS_PER_FONT; i++) {
+			font->glyphs[i].height		= readInt();
+			font->glyphs[i].top			= readInt();
+			font->glyphs[i].bottom		= readInt();
+			font->glyphs[i].pitch		= readInt();
+			font->glyphs[i].xSkip		= readInt();
+			font->glyphs[i].imageWidth	= readInt();
+			font->glyphs[i].imageHeight = readInt();
+			font->glyphs[i].s			= readFloat();
+			font->glyphs[i].t			= readFloat();
+			font->glyphs[i].s2			= readFloat();
+			font->glyphs[i].t2			= readFloat();
+			font->glyphs[i].glyph		= readInt();
+			Q_strncpyz(font->glyphs[i].shaderName, (const char *)&fdFile[fdOffset], sizeof(font->glyphs[i].shaderName));
+			fdOffset += sizeof(font->glyphs[i].shaderName);
+		}
+		font->glyphScale = readFloat();
+		Com_Memcpy(font->name, &fdFile[fdOffset], MAX_QPATH);
+
+//		Com_Memcpy(font, faceData, sizeof(fontInfo_t));
+		Q_strncpyz(font->name, name, sizeof(font->name));
+		for (i = GLYPH_START; i < GLYPH_END; i++) {
+			font->glyphs[i].glyph = RE_RegisterShaderNoMip(font->glyphs[i].shaderName);
+		}
+		Com_Memcpy(&registeredFont[registeredFontCount++], font, sizeof(fontInfo_t));
+		return;
+	}
+
+#ifndef BUILD_FREETYPE
+	ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType code not available\n");
+#else
+	if (ftLibrary == NULL) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType not initialized.\n");
+		return;
+	}
+
+	len = ri.FS_ReadFile(fontName, &faceData);
+	if (len <= 0) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: Unable to read font file '%s'\n", fontName);
+		return;
+	}
+
+	// allocate on the stack first in case we fail
+	if (FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face )) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to allocate new face.\n");
+		return;
+	}
+
+
+	if (FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi)) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to set face char size.\n");
+		return;
+	}
+
+	//*font = &registeredFonts[registeredFontCount++];
+
+	// make a 256x256 image buffer, once it is full, register it, clean it and keep going 
+	// until all glyphs are rendered
+
+	out = ri.Malloc(1024*1024);
+	if (out == NULL) {
+		ri.Printf(PRINT_WARNING, "RE_RegisterFont: ri.Malloc failure during output image creation.\n");
+		return;
+	}
+	Com_Memset(out, 0, 1024*1024);
+
+	maxHeight = 0;
+
+	for (i = GLYPH_START; i < GLYPH_END; i++) {
+		RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue);
+	}
+
+	xOut = 0;
+	yOut = 0;
+	i = GLYPH_START;
+	lastStart = i;
+	imageNumber = 0;
+
+	while ( i <= GLYPH_END ) {
+
+		glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse);
+
+		if (xOut == -1 || yOut == -1 || i == GLYPH_END)  {
+			// ran out of room
+			// we need to create an image from the bitmap, set all the handles in the glyphs to this point
+			// 
+
+			scaledSize = 256*256;
+			newSize = scaledSize * 4;
+			imageBuff = ri.Malloc(newSize);
+			left = 0;
+			max = 0;
+			for ( k = 0; k < (scaledSize) ; k++ ) {
+				if (max < out[k]) {
+					max = out[k];
+				}
+			}
+
+			if (max > 0) {
+				max = 255/max;
+			}
+
+			for ( k = 0; k < (scaledSize) ; k++ ) {
+				imageBuff[left++] = 255;
+				imageBuff[left++] = 255;
+				imageBuff[left++] = 255;
+
+				imageBuff[left++] = ((float)out[k] * max);
+			}
+
+			Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize);
+			if (r_saveFontData->integer) { 
+				WriteTGA(name, imageBuff, 256, 256);
+			}
+
+			//Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize);
+			image = R_CreateImage(name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP_TO_EDGE);
+			h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse);
+			for (j = lastStart; j < i; j++) {
+				font->glyphs[j].glyph = h;
+				Q_strncpyz(font->glyphs[j].shaderName, name, sizeof(font->glyphs[j].shaderName));
+			}
+			lastStart = i;
+			Com_Memset(out, 0, 1024*1024);
+			xOut = 0;
+			yOut = 0;
+			ri.Free(imageBuff);
+			i++;
+		} else {
+			Com_Memcpy(&font->glyphs[i], glyph, sizeof(glyphInfo_t));
+			i++;
+		}
+	}
+
+	// change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 )
+	glyphScale = 72.0f / dpi;
+
+	// we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font
+	glyphScale *= 48.0f / pointSize;
+
+	registeredFont[registeredFontCount].glyphScale = glyphScale;
+	font->glyphScale = glyphScale;
+	Com_Memcpy(&registeredFont[registeredFontCount++], font, sizeof(fontInfo_t));
+
+	if (r_saveFontData->integer) {
+		ri.FS_WriteFile(va("fonts/fontImage_%i.dat", pointSize), font, sizeof(fontInfo_t));
+	}
+
+	ri.Free(out);
+
+	ri.FS_FreeFile(faceData);
+#endif
+}
+
+
+
+void R_InitFreeType(void) {
+#ifdef BUILD_FREETYPE
+	if (FT_Init_FreeType( &ftLibrary )) {
+		ri.Printf(PRINT_WARNING, "R_InitFreeType: Unable to initialize FreeType.\n");
+	}
+#endif
+	registeredFontCount = 0;
+}
+
+
+void R_DoneFreeType(void) {
+#ifdef BUILD_FREETYPE
+	if (ftLibrary) {
+		FT_Done_FreeType( ftLibrary );
+		ftLibrary = NULL;
+	}
+#endif
+	registeredFontCount = 0;
+}
+

Added: trunk/code/rend2/tr_glsl.c
===================================================================
--- trunk/code/rend2/tr_glsl.c	                        (rev 0)
+++ trunk/code/rend2/tr_glsl.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,2825 @@
+/*
+===========================================================================
+Copyright (C) 2006-2009 Robert Beckebans <trebor_7 at users.sourceforge.net>
+
+This file is part of XreaL source code.
+
+XreaL source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+XreaL source code 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 XreaL source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_glsl.c
+#include "tr_local.h"
+
+void GLSL_BindNullProgram(void);
+
+// FIXME: Do something that isn't this messy
+static const char *fallbackGenericShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\nattribut"
+"e vec4 attr_TexCoord1;\r\nattribute vec3 attr_Normal;\r\nattribute vec4 att"
+"r_Color;\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nattribute vec4 attr_Po"
+"sition2;\r\nattribute vec3 attr_Normal2;\r\n#endif\r\n\r\nuniform mat4   u_"
+"DiffuseTexMatrix;\r\nuniform vec3   u_ViewOrigin;\r\n\r\n#if defined(USE_TC"
+"GEN)\r\nuniform int    u_TCGen0;\r\nuniform vec3   u_TCGen0Vector0;\r\nunif"
+"orm vec3   u_TCGen0Vector1;\r\n#endif\r\n\r\n#if defined(USE_FOG)\r\nunifor"
+"m vec4   u_FogDistance;\r\nuniform vec4   u_FogDepth;\r\nuniform float  u_F"
+"ogEyeT;\r\nuniform vec4   u_FogColorMask;\r\n#endif\r\n\r\n#if defined(USE_"
+"DEFORM_VERTEXES)\r\nuniform int    u_DeformGen;\r\nuniform float  u_DeformP"
+"arams[5];\r\n#endif\r\n\r\nuniform float  u_Time;\r\n\r\nuniform mat4   u_M"
+"odelViewProjectionMatrix;\r\nuniform vec4   u_BaseColor;\r\nuniform vec4   "
+"u_VertColor;\r\n\r\n#if defined(USE_RGBAGEN)\r\nuniform int    u_ColorGen;"
+"\r\nuniform int    u_AlphaGen;\r\nuniform vec3   u_AmbientLight;\r\nuniform"
+" vec3   u_DirectedLight;\r\nuniform vec4   u_LightOrigin;\r\nuniform float "
+" u_PortalRange;\r\n#endif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nunifo"
+"rm float  u_VertexLerp;\r\n#endif\r\n\r\nvarying vec2   var_DiffuseTex;\r\n"
+"#if defined(USE_LIGHTMAP)\r\nvarying vec2   var_LightTex;\r\n#endif\r\nvary"
+"ing vec4   var_Color;\r\n\r\nvec2 DoTexMatrix(vec2 st, vec3 position, mat4 "
+"texMatrix)\r\n{\r\n\tfloat amplitude = texMatrix[3][0];\r\n\tfloat phase = "
+"texMatrix[3][1];\r\n\tvec2 st2 = (texMatrix * vec4(st, 1.0, 0.0)).st;\r\n\r"
+"\n\tvec3 offsetPos = position.xyz / 1024.0;\r\n\toffsetPos.x += offsetPos.z"
+";\r\n\r\n\tvec2 texOffset = sin((offsetPos.xy + vec2(phase)) * 2.0 * M_PI);"
+"\r\n\t\r\n\treturn st2 + texOffset * amplitude;\r\n}\r\n\r\n#if defined(USE"
+"_DEFORM_VERTEXES)\r\nfloat triangle(float x)\r\n{\r\n\treturn max(1.0 - abs"
+"(x), 0);\r\n}\r\n\r\nfloat sawtooth(float x)\r\n{\r\n\treturn x - floor(x);"
+"\r\n}\r\n\r\nvec4 DeformPosition(const vec4 pos, const vec3 normal, const v"
+"ec2 st)\r\n{\r\n\tfloat base =      u_DeformParams[0];\r\n\tfloat amplitude"
+" = u_DeformParams[1];\r\n\tfloat phase =     u_DeformParams[2];\r\n\tfloat "
+"frequency = u_DeformParams[3];\r\n\tfloat spread =    u_DeformParams[4];\r"
+"\n\t\r\n\tif (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M_PI * 0.25"
+" * st.x;\r\n\t}\r\n\telse // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)"
+"\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * spread;\r\n\t}\r\n\r\n\tf"
+"loat value = phase + (u_Time * frequency);\r\n\tfloat func;\r\n\r\n\tif (u_"
+"DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n\t\tfunc = sin(value * 2.0 * M_PI);\r"
+"\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = s"
+"ign(sin(value * 2.0 * M_PI));\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE"
+"_TRIANGLE)\r\n\t{\r\n\t\tfunc = triangle(value);\r\n\t}\r\n\telse if (u_Def"
+"ormGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = sawtooth(value);\r\n\t}"
+"\r\n\telse if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfun"
+"c = (1.0 - sawtooth(value));\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE"
+")\r\n\t{\r\n\t\tfunc = sin(value);\r\n\t}\r\n\t\r\n\tvec4 deformed = pos;\r"
+"\n\tdeformed.xyz += normal * (base + func * amplitude);\r\n\r\n\treturn def"
+"ormed;\r\n}\r\n#endif\r\n\r\n#if defined(USE_TCGEN)\r\nvec2 GenTexCoords(in"
+"t TCGen, vec4 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1)"
+"\r\n{\r\n\tvec2 tex = attr_TexCoord0.st;\r\n\r\n\tif (TCGen == TCGEN_LIGHTM"
+"AP)\r\n\t{\r\n\t\ttex = attr_TexCoord1.st;\r\n\t}\r\n\telse if (TCGen == TC"
+"GEN_ENVIRONMENT_MAPPED)\r\n\t{\r\n\t\tvec3 viewer = normalize(u_ViewOrigin "
+"- position.xyz);\r\n\t\tvec3 reflected = normal * 2.0 * dot(normal, viewer)"
+" - viewer;\r\n\r\n\t\ttex = reflected.yz * vec2(0.5, -0.5) + 0.5;\r\n\t}\r"
+"\n\telse if (TCGen == TCGEN_VECTOR)\r\n\t{\r\n\t\ttex = vec2(dot(position.x"
+"yz, TCGenVector0), dot(position.xyz, TCGenVector1));\r\n\t}\r\n\t\r\n\tretu"
+"rn tex;\r\n}\r\n#endif\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_VERTEX_AN"
+"IMATION)\r\n\tvec4 position = mix(attr_Position, attr_Position2, u_VertexLe"
+"rp);\r\n\tvec3 normal = normalize(mix(attr_Normal, attr_Normal2, u_VertexLe"
+"rp));\r\n#else\r\n\tvec4 position = attr_Position;\r\n\tvec3 normal = attr_"
+"Normal;\r\n#endif\r\n\r\n#if defined(USE_DEFORM_VERTEXES)\r\n\tposition = D"
+"eformPosition(position, normal, attr_TexCoord0.st);\r\n#endif\r\n\r\n\tgl_P"
+"osition = u_ModelViewProjectionMatrix * position;\r\n\r\n#if defined(USE_TC"
+"GEN)\r\n\tvec2 tex = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vecto"
+"r0, u_TCGen0Vector1);\r\n#else\r\n\tvec2 tex = attr_TexCoord0.st;\r\n#endif"
+"\r\n\tvar_DiffuseTex = DoTexMatrix(tex, position.xyz, u_DiffuseTexMatrix);"
+"\r\n\r\n#if defined(USE_LIGHTMAP)\r\n\tvar_LightTex = attr_TexCoord1.st;\r"
+"\n#endif\r\n\r\n\tvar_Color = u_VertColor * attr_Color + u_BaseColor;\r\n\r"
+"\n#if defined(USE_RGBAGEN)\r\n\tif (u_ColorGen == CGEN_LIGHTING_DIFFUSE)\r"
+"\n\t{\r\n\t\tfloat incoming = max(dot(normal, u_LightOrigin.xyz), 0.0);\r\n"
+"\r\n\t\tvar_Color.rgb = min(u_DirectedLight * incoming + u_AmbientLight, 1."
+"0);\r\n\t}\r\n\t\r\n\tvec3 toView = u_ViewOrigin - position.xyz;\r\n\r\n\ti"
+"f (u_AlphaGen == AGEN_LIGHTING_SPECULAR)\r\n\t{\r\n\t\tvec3 lightDir = norm"
+"alize(vec3(-960.0, -1980.0, 96.0) - position.xyz);\r\n\t\tvec3 viewer = nor"
+"malize(toView);\r\n\t\tvec3 halfangle = normalize(lightDir + viewer);\r\n\t"
+"\t\r\n\t\tvar_Color.a = pow(max(dot(normal, halfangle), 0.0), 8.0);\r\n\t}"
+"\r\n\telse if (u_AlphaGen == AGEN_PORTAL)\r\n\t{\r\n\t\tfloat alpha = lengt"
+"h(toView) / u_PortalRange;\r\n\r\n\t\tvar_Color.a = min(alpha, 1.0);\r\n\t}"
+"\r\n\telse if (u_AlphaGen == AGEN_FRESNEL)\r\n\t{\r\n\t\tvec3 viewer = norm"
+"alize(toView);\r\n\t\t\r\n\t\tvar_Color.a = 0.10 + 0.90 * pow(1.0 - dot(nor"
+"mal, viewer), 5);\r\n\t}\r\n#endif\r\n\r\n#if defined (USE_FOG)\r\n\tfloat "
+"s = dot(position, u_FogDistance);\r\n\tfloat t = dot(position, u_FogDepth);"
+"\r\n\t\r\n\tif (t >= 1.0)\r\n\t{\r\n\t\ts *= t / (t - min(u_FogEyeT, 0.0));"
+"\r\n\t}\r\n\telse\r\n\t{\r\n\t\ts *= max(t + sign(u_FogEyeT), 0.0);\r\n\t}"
+"\r\n\t\r\n\ts = 1.0 - sqrt(clamp(s * 8.0, 0.0, 1.0));\r\n\t\r\n\tvar_Color "
+"*= u_FogColorMask * s + (vec4(1.0) - u_FogColorMask);\r\n#endif\r\n}\r\n";
+
+static const char *fallbackGenericShader_fp =
+"uniform sampler2D u_DiffuseMap;\r\n\r\n#if defined(USE_LIGHTMAP)\r\nuniform"
+" sampler2D u_LightMap;\r\n#endif\r\n\r\nuniform int       u_Texture1Env;\r"
+"\n\r\nvarying vec2      var_DiffuseTex;\r\n\r\n#if defined(USE_LIGHTMAP)\r"
+"\nvarying vec2      var_LightTex;\r\n#endif\r\n\r\nvarying vec4      var_Co"
+"lor;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 color  = texture2D(u_DiffuseMap,"
+" var_DiffuseTex);\r\n#if defined(USE_LIGHTMAP)\r\n\tvec4 color2 = texture2D"
+"(u_LightMap, var_LightTex);\r\n  #if defined(RGBE_LIGHTMAP)\r\n\tcolor2.rgb"
+" *= exp2(color2.a * 255.0 - 128.0);\r\n\tcolor2.a = 1.0;\r\n  #endif\r\n\r"
+"\n\tif (u_Texture1Env == TEXENV_MODULATE)\r\n\t{\r\n\t\tcolor *= color2;\r"
+"\n\t}\r\n\telse if (u_Texture1Env == TEXENV_ADD)\r\n\t{\r\n\t\tcolor += col"
+"or2;\r\n\t}\r\n\telse if (u_Texture1Env == TEXENV_REPLACE)\r\n\t{\r\n\t\tco"
+"lor = color2;\r\n\t}\r\n#endif\r\n\r\n\tgl_FragColor = color * var_Color;\r"
+"\n}\r\n";
+
+static const char *fallbackTextureColorShader_vp =
+"#version 120\r\n\r\nattribute vec4 attr_Position;\r\nattribute vec4 attr_Te"
+"xCoord0;\r\n\r\nuniform mat4   u_ModelViewProjectionMatrix;\r\n\r\nvarying "
+"vec2   var_Tex1;\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelView"
+"ProjectionMatrix * attr_Position;\r\n\tvar_Tex1 = attr_TexCoord0.st;\r\n}\r"
+"\n";
+
+static const char *fallbackTextureColorShader_fp =
+"#version 120\r\n\r\nuniform sampler2D u_DiffuseMap;\r\nuniform vec4      u_"
+"Color;\r\n\r\nvarying vec2         var_Tex1;\r\n\r\n\r\nvoid main()\r\n{\r"
+"\n\tgl_FragColor = texture2D(u_DiffuseMap, var_Tex1) * u_Color;\r\n}\r\n";
+
+static const char *fallbackFogPassShader_vp =
+"attribute vec4  attr_Position;\r\nattribute vec3  attr_Normal;\r\nattribute"
+" vec4  attr_TexCoord0;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nattrib"
+"ute vec4  attr_Position2;\r\nattribute vec3  attr_Normal2;\r\n//#endif\r\n"
+"\r\nuniform vec4    u_FogDistance;\r\nuniform vec4    u_FogDepth;\r\nunifor"
+"m float   u_FogEyeT;\r\n\r\n//#if defined(USE_DEFORM_VERTEXES)\r\nuniform i"
+"nt     u_DeformGen;\r\nuniform float   u_DeformParams[5];\r\n//#endif\r\n\r"
+"\nuniform float   u_Time;\r\nuniform mat4    u_ModelViewProjectionMatrix;\r"
+"\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nuniform float   u_VertexLerp;"
+"\r\n//#endif\r\n\r\nvarying float   var_Scale;\r\n\r\n\r\nfloat triangle(fl"
+"oat x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\nfloat sawtooth(fl"
+"oat x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 DeformPosition(const"
+" vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif (u_DeformGen == 0"
+")\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base =      u_DeformParam"
+"s[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat phase =     u_De"
+"formParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r\n\tfloat spread "
+"=    u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DGEN_WAVE_INVERSE_S"
+"AWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * spread;\r\n\t}\r"
+"\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M_PI * 0.25 "
+"* st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * frequency);\r\n\tfl"
+"oat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n\t\tfunc = s"
+"in(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SQUAR"
+"E)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n\t}\r\n\telse if "
+"(u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = triangle(value);\r"
+"\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r\n\t\tfunc ="
+" sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_INVERSE_SAW"
+"TOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t}\r\n\telse if (u"
+"_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);\r\n\t}\r\n\r\n\t"
+"vec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base + func * amplitude"
+");\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec4 pos"
+"ition = mix(attr_Position, attr_Position2, u_VertexLerp);\r\n\tvec3 normal "
+"= normalize(mix(attr_Normal, attr_Normal2, u_VertexLerp));\r\n\r\n\tpositio"
+"n = DeformPosition(position, normal, attr_TexCoord0.st);\r\n\r\n\tgl_Positi"
+"on = u_ModelViewProjectionMatrix * position;\r\n\r\n\tfloat s = dot(positio"
+"n, u_FogDistance);\r\n\tfloat t = dot(position, u_FogDepth);\r\n\r\n\tif (t"
+" >= 1.0)\r\n\t{\r\n\t\ts *= t / (t - min(u_FogEyeT, 0.0));\r\n\t}\r\n\telse"
+"\r\n\t{\r\n\t\ts *= max(t + sign(u_FogEyeT), 0.0);\r\n\t}\r\n\r\n\tvar_Scal"
+"e = s * 8.0;\r\n}\r\n";
+
+static const char *fallbackFogPassShader_fp =
+"uniform vec4  u_Color;\r\n\r\nvarying float var_Scale;\r\n\r\nvoid main()\r"
+"\n{\r\n\tgl_FragColor = u_Color;\r\n\tgl_FragColor.a *= sqrt(clamp(var_Scal"
+"e, 0.0, 1.0));\r\n}\r\n";
+
+static const char *fallbackDlightShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\nattribut"
+"e vec3 attr_Normal;\r\n\r\nuniform vec4   u_DlightInfo;\r\n\r\nuniform int "
+"   u_DeformGen;\r\nuniform float  u_DeformParams[5];\r\n\r\nuniform float  "
+"u_Time;\r\nuniform vec4   u_Color;\r\nuniform mat4   u_ModelViewProjectionM"
+"atrix;\r\n\r\nvarying vec2   var_Tex1;\r\nvarying vec4   var_Color;\r\n\r\n"
+"float triangle(float x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\n"
+"float sawtooth(float x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 Def"
+"ormPosition(const vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif "
+"(u_DeformGen == 0)\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base =  "
+"    u_DeformParams[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat"
+" phase =     u_DeformParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r"
+"\n\tfloat spread =    u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DG"
+"EN_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * "
+"spread;\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase"
+" *= M_PI * 0.25 * st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * fre"
+"quency);\r\n\tfloat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{"
+"\r\n\t\tfunc = sin(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen =="
+" DGEN_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n"
+"\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = t"
+"riangle(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n"
+"\t{\r\n\t\tfunc = sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN"
+"_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t"
+"}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);"
+"\r\n\t}\r\n\r\n\tvec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base +"
+" func * amplitude);\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\nvoid main()\r"
+"\n{\r\n\tvec4 position = attr_Position;\r\n\tvec3 normal = attr_Normal;\r\n"
+"\r\n\tposition = DeformPosition(position, normal, attr_TexCoord0.st);\r\n\r"
+"\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\t\t\r\n\tvec3"
+" dist = u_DlightInfo.xyz - position.xyz;\t\r\n\r\n\tfloat diffz = abs(dist."
+"z);\r\n\tfloat radius = 1.0 / u_DlightInfo.a;\r\n\r\n\tvec2 tex = vec2(0.5)"
+" + dist.xy * u_DlightInfo.a;\r\n\tfloat dlightmod = max(sign(dot(dist, norm"
+"al)), 0.0);\r\n\tdlightmod *= clamp(2.0 * (radius - diffz) * u_DlightInfo.a"
+", 0.0, 1.0);\r\n\r\n\tvar_Tex1 = tex;\r\n\tvar_Color = u_Color;\r\n\tvar_Co"
+"lor.rgb *= dlightmod;\r\n}\r\n";
+
+static const char *fallbackDlightShader_fp =
+"uniform sampler2D u_DiffuseMap;\r\n\r\nvarying vec2      var_Tex1;\r\nvaryi"
+"ng vec4      var_Color;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 color = textu"
+"re2D(u_DiffuseMap, var_Tex1);\r\n\r\n\tgl_FragColor = color * var_Color;\r"
+"\n}\r\n";
+
+static const char *fallbackLightallShader_vp =
+"attribute vec4 attr_TexCoord0;\r\n#if defined(USE_LIGHTMAP)\r\nattribute ve"
+"c4 attr_TexCoord1;\r\n#endif\r\nattribute vec4 attr_Color;\r\n\r\nattribute"
+" vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\n\r\n#if defined(USE_"
+"VERT_TANGENT_SPACE)\r\nattribute vec3 attr_Tangent;\r\nattribute vec3 attr_"
+"Bitangent;\r\n#endif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nattribute "
+"vec4 attr_Position2;\r\nattribute vec3 attr_Normal2;\r\n  #if defined(USE_V"
+"ERT_TANGENT_SPACE)\r\nattribute vec3 attr_Tangent2;\r\nattribute vec3 attr_"
+"Bitangent2;\r\n  #endif\r\n#endif\r\n\r\n#if defined(USE_LIGHT) && !defined"
+"(USE_LIGHT_VECTOR)\r\nattribute vec3 attr_LightDirection;\r\n#endif\r\n\r\n"
+"#if defined(TCGEN_ENVIRONMENT) || defined(USE_NORMALMAP) || defined(USE_LIG"
+"HT) && !defined(USE_FAST_LIGHT)\r\nuniform vec3   u_ViewOrigin;\r\n#endif\r"
+"\n\r\nuniform mat4   u_DiffuseTexMatrix;\r\nuniform mat4   u_ModelViewProje"
+"ctionMatrix;\r\nuniform vec4   u_BaseColor;\r\nuniform vec4   u_VertColor;"
+"\r\n\r\n#if defined(USE_MODELMATRIX)\r\nuniform mat4   u_ModelMatrix;\r\n#e"
+"ndif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nuniform float  u_VertexLer"
+"p;\r\n#endif\r\n\r\n#if defined(USE_LIGHT_VECTOR)\r\nuniform vec4   u_Light"
+"Origin;\r\n  #if defined(USE_FAST_LIGHT)\r\nuniform vec3   u_DirectedLight;"
+"\r\nuniform vec3   u_AmbientLight;\r\nuniform float  u_LightRadius;\r\n  #e"
+"ndif\r\n#endif\r\n\r\nvarying vec2   var_DiffuseTex;\r\n\r\n#if defined(USE"
+"_LIGHTMAP)\r\nvarying vec2   var_LightTex;\r\n#endif\r\n\r\n#if defined(USE"
+"_NORMALMAP) || defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)\r\nvarying ve"
+"c3   var_SampleToView;\r\n#endif\r\n\r\nvarying vec4   var_Color;\r\nvaryin"
+"g vec3   var_Position;\r\nvarying vec3   var_Normal;\r\n\r\n#if defined(USE"
+"_VERT_TANGENT_SPACE)\r\nvarying vec3   var_Tangent;\r\nvarying vec3   var_B"
+"itangent;\r\n#endif\r\n\r\nvarying vec3   var_VertLight;\r\n\r\n#if defined"
+"(USE_LIGHT) && !defined(USE_DELUXEMAP)\r\nvarying vec3   var_WorldLight;\r"
+"\n#endif\r\n\r\nvarying vec4   var_ScreenPos;\r\n\r\nvec2 DoTexMatrix(vec2 "
+"st, vec3 position, mat4 texMatrix)\r\n{\r\n\tvec2 st2 = (texMatrix * vec4(s"
+"t, 1, 0)).st;\r\n\r\n\tvec3 offsetPos = position.xyz / 1024.0;\r\n\toffsetP"
+"os.x += offsetPos.z;\r\n\r\n\tvec2 texOffset = sin((offsetPos.xy + vec2(tex"
+"Matrix[3][1])) * 2.0 * M_PI);\r\n\t\r\n\treturn st2 + texOffset * texMatrix"
+"[3][0];\r\n}\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_VERTEX_ANIMATION)\r"
+"\n\tvec4 position  = mix(attr_Position, attr_Position2, u_VertexLerp);\r\n"
+"\tvec3 normal    = normalize(mix(attr_Normal,    attr_Normal2,    u_VertexL"
+"erp));\r\n  #if defined(USE_VERT_TANGENT_SPACE)\r\n\tvec3 tangent   = norma"
+"lize(mix(attr_Tangent,   attr_Tangent2,   u_VertexLerp));\r\n\tvec3 bitange"
+"nt = normalize(mix(attr_Bitangent, attr_Bitangent2, u_VertexLerp));\r\n  #e"
+"ndif\r\n#else\r\n\tvec4 position  = attr_Position;\r\n\tvec3 normal    = at"
+"tr_Normal;\r\n  #if defined(USE_VERT_TANGENT_SPACE)\r\n\tvec3 tangent   = a"
+"ttr_Tangent;\r\n\tvec3 bitangent = attr_Bitangent;\r\n  #endif\r\n#endif\r"
+"\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\tvar_Scre"
+"enPos = gl_Position;\r\n\r\n#if (defined(USE_LIGHTMAP) || defined(USE_LIGHT"
+"_VERTEX)) && !defined(USE_DELUXEMAP)\r\n\tvec3 worldLight = attr_LightDirec"
+"tion;\r\n#endif\r\n\t\r\n#if defined(USE_MODELMATRIX)\r\n\tposition  = u_Mo"
+"delMatrix * position;\r\n\tnormal    = (u_ModelMatrix * vec4(normal, 0.0))."
+"xyz;\r\n  #if defined(USE_VERT_TANGENT_SPACE)\r\n\ttangent   = (u_ModelMatr"
+"ix * vec4(tangent, 0.0)).xyz;\r\n\tbitangent = (u_ModelMatrix * vec4(bitang"
+"ent, 0.0)).xyz;\r\n  #endif\r\n\r\n  #if defined(USE_LIGHTMAP) && !defined("
+"USE_DELUXEMAP)\r\n\tworldLight = (u_ModelMatrix * vec4(worldLight, 0.0)).xy"
+"z;\r\n  #endif\r\n#endif\r\n\r\n\tvar_Position = position.xyz;\r\n\r\n#if d"
+"efined(TCGEN_ENVIRONMENT) || defined(USE_NORMALMAP) || defined(USE_LIGHT) &"
+"& !defined(USE_FAST_LIGHT)\r\n\tvec3 SampleToView = u_ViewOrigin - position"
+".xyz;\r\n#endif\r\n\r\n#if defined(USE_NORMALMAP) || defined(USE_LIGHT) && "
+"!defined(USE_FAST_LIGHT)\r\n\tvar_SampleToView = SampleToView;\r\n#endif\r"
+"\n\r\n#if defined(TCGEN_ENVIRONMENT)\r\n\tvec3 viewer = normalize(SampleToV"
+"iew);\r\n\tvec3 reflected = normal * 2.0 * dot(normal, viewer) - viewer;\r"
+"\n\r\n\tvec2 tex = reflected.yz * vec2(0.5, -0.5) + 0.5;\r\n#else\r\n\tvec2"
+" tex = attr_TexCoord0.st;\r\n#endif\r\n\r\n\tvar_DiffuseTex = DoTexMatrix(t"
+"ex, position.xyz, u_DiffuseTexMatrix);\r\n\r\n#if defined(USE_LIGHTMAP)\r\n"
+"\tvar_LightTex = attr_TexCoord1.st;\r\n#endif\r\n  \r\n\tvar_Normal = norma"
+"l;\r\n#if defined(USE_VERT_TANGENT_SPACE)\r\n\tvar_Tangent = tangent;\r\n\t"
+"var_Bitangent = bitangent;\r\n#endif\r\n\r\n#if defined(USE_LIGHT) && !defi"
+"ned(USE_DELUXEMAP)\r\n  #if defined(USE_LIGHT_VECTOR)\r\n\tvec3 worldLight "
+"= u_LightOrigin.xyz - (position.xyz * u_LightOrigin.w);\r\n  #endif\r\n\r\n"
+"\tworldLight += normal * 0.0001;\r\n\tvar_WorldLight = worldLight;\r\n#endi"
+"f\r\n\t\r\n#if defined(USE_LIGHT_VERTEX)\r\n    var_VertLight = attr_Color."
+"rgb;\r\n  #if !defined(USE_FAST_LIGHT)\r\n\tvar_VertLight /= max(dot(normal"
+", normalize(worldLight)), 0.004);\r\n  #endif\r\n\tvar_Color.rgb = u_BaseCo"
+"lor.rgb;\r\n\tvar_Color.a = u_VertColor.a * attr_Color.a + u_BaseColor.a;\r"
+"\n#else\r\n\tvar_Color = u_VertColor * attr_Color + u_BaseColor;\r\n#endif"
+"\r\n\r\n#if defined(USE_LIGHT_VECTOR) && defined(USE_FAST_LIGHT)\r\n  #if d"
+"efined(USE_INVSQRLIGHT)\r\n\tfloat intensity = 1.0 / dot(worldLight, worldL"
+"ight);\r\n  #else\r\n\tfloat intensity = clamp((1.0 - dot(worldLight, world"
+"Light) / (u_LightRadius * u_LightRadius)) * 1.07, 0.0, 1.0);\r\n  #endif\r"
+"\n\tfloat NL = clamp(dot(normal, normalize(worldLight)), 0.0, 1.0);\r\n\r\n"
+"\tvar_VertLight = u_DirectedLight * intensity * NL + u_AmbientLight;\r\n#en"
+"dif\r\n}\r\n";
+
+static const char *fallbackLightallShader_fp =
+"uniform sampler2D u_DiffuseMap;\r\n\r\n#if defined(USE_LIGHTMAP)\r\nuniform"
+" sampler2D u_LightMap;\r\n#endif\r\n\r\n#if defined(USE_NORMALMAP)\r\nunifo"
+"rm sampler2D u_NormalMap;\r\n#endif\r\n\r\n#if defined(USE_DELUXEMAP)\r\nun"
+"iform sampler2D u_DeluxeMap;\r\n#endif\r\n\r\n#if defined(USE_SPECULARMAP)"
+"\r\nuniform sampler2D u_SpecularMap;\r\n#endif\r\n\r\n#if defined(USE_SHADO"
+"WMAP)\r\nuniform sampler2D u_ShadowMap;\r\n#endif\r\n\r\nuniform vec3      "
+"u_ViewOrigin;\r\n\r\n#if defined(USE_LIGHT_VECTOR)\r\nuniform vec3      u_D"
+"irectedLight;\r\nuniform vec3      u_AmbientLight;\r\nuniform float     u_L"
+"ightRadius;\r\n#endif\r\n\r\n#if defined(USE_LIGHT)\r\nuniform vec2      u_"
+"MaterialInfo;\r\n#endif\r\n\r\nvarying vec2      var_DiffuseTex;\r\n#if def"
+"ined(USE_LIGHTMAP)\r\nvarying vec2      var_LightTex;\r\n#endif\r\nvarying "
+"vec4      var_Color;\r\nvarying vec3      var_Position;\r\n\r\nvarying vec3"
+"      var_SampleToView;\r\n\r\nvarying vec3      var_Normal;\r\n#if defined"
+"(USE_VERT_TANGENT_SPACE)\r\nvarying vec3      var_Tangent;\r\nvarying vec3 "
+"     var_Bitangent;\r\n#endif\r\n\r\nvarying vec3      var_VertLight;\r\n\r"
+"\n#if defined(USE_LIGHT) && !defined(USE_DELUXEMAP)\r\nvarying vec3      va"
+"r_WorldLight;\r\n#endif\r\n\r\nvarying vec4   var_ScreenPos;\r\n\r\n#define"
+" EPSILON 0.00000001\r\n\r\n#if defined(USE_PARALLAXMAP)\r\nfloat SampleHeig"
+"ht(sampler2D normalMap, vec2 t)\r\n{\r\n  #if defined(SWIZZLE_NORMALMAP)\r"
+"\n\treturn texture2D(normalMap, t).r;\r\n  #else\r\n\treturn texture2D(norm"
+"alMap, t).a;\r\n  #endif\r\n}\r\n\r\nfloat RayIntersectDisplaceMap(vec2 dp,"
+" vec2 ds, sampler2D normalMap)\r\n{\r\n\tconst int linearSearchSteps = 16;"
+"\r\n\tconst int binarySearchSteps = 6;\r\n\r\n\tfloat depthStep = 1.0 / flo"
+"at(linearSearchSteps);\r\n\r\n\t// current size of search window\r\n\tfloat"
+" size = depthStep;\r\n\r\n\t// current depth position\r\n\tfloat depth = 0."
+"0;\r\n\r\n\t// best match found (starts with last position 1.0)\r\n\tfloat "
+"bestDepth = 1.0;\r\n\r\n\t// search front to back for first point inside ob"
+"ject\r\n\tfor(int i = 0; i < linearSearchSteps - 1; ++i)\r\n\t{\r\n\t\tdept"
+"h += size;\r\n\t\t\r\n\t\tfloat t = 1.0 - SampleHeight(normalMap, dp + ds *"
+" depth);\r\n\t\t\r\n\t\tif(bestDepth > 0.996)\t\t// if no depth found yet\r"
+"\n\t\t\tif(depth >= t)\r\n\t\t\t\tbestDepth = depth;\t// store best depth\r"
+"\n\t}\r\n\r\n\tdepth = bestDepth;\r\n\t\r\n\t// recurse around first point "
+"(depth) for closest match\r\n\tfor(int i = 0; i < binarySearchSteps; ++i)\r"
+"\n\t{\r\n\t\tsize *= 0.5;\r\n\r\n\t\tfloat t = 1.0 - SampleHeight(normalMap"
+", dp + ds * depth);\r\n\t\t\r\n\t\tif(depth >= t)\r\n\t\t{\r\n\t\t\tbestDep"
+"th = depth;\r\n\t\t\tdepth -= 2.0 * size;\r\n\t\t}\r\n\r\n\t\tdepth += size"
+";\r\n\t}\r\n\r\n\treturn bestDepth;\r\n}\r\n#endif\r\n\r\nfloat CalcDiffuse"
+"(vec3 N, vec3 L, vec3 E, float NE, float NL, float fzero, float shininess)"
+"\r\n{\r\n  #if defined(USE_OREN_NAYAR) || defined(USE_TRIACE_OREN_NAYAR)\r"
+"\n\tfloat gamma = dot(E, L) - NE * NL;\r\n\tfloat B = 2.22222 + 0.1 * shini"
+"ness;\r\n\t\t\r\n\t#if defined(USE_OREN_NAYAR)\r\n\tfloat A = 1.0 - 1.0 / ("
+"2.0 + 0.33 * shininess);\r\n\tgamma = clamp(gamma, 0.0, 1.0);\r\n\t#endif\r"
+"\n\t\r\n\t#if defined(USE_TRIACE_OREN_NAYAR)\r\n\tfloat A = 1.0 - 1.0 / (2."
+"0 + 0.65 * shininess);\r\n\r\n\tif (gamma >= 0.0)\r\n\t#endif\r\n\t{\r\n\t"
+"\tB *= max(max(NL, NE), EPSILON);\r\n\t}\r\n\r\n\treturn A + gamma / B;\r\n"
+"  #else\r\n\treturn 1.0 - fzero;\r\n  #endif\r\n}\r\n\r\n#if defined(USE_SP"
+"ECULARMAP)\r\nfloat CalcSpecular(float NH, float NL, float NE, float EH, fl"
+"oat fzero, float shininess)\r\n{\r\n  #if defined(USE_BLINN) || defined(USE"
+"_TRIACE) || defined(USE_TORRANCE_SPARROW)\r\n\tfloat blinn = pow(NH, shinin"
+"ess);\r\n  #endif\r\n\r\n  #if defined(USE_BLINN)\r\n\treturn blinn;\r\n  #"
+"endif\r\n\r\n  #if defined(USE_COOK_TORRANCE) || defined (USE_TRIACE) || de"
+"fined (USE_TORRANCE_SPARROW)\r\n\tfloat fresnel = fzero + (1.0 - fzero) * p"
+"ow(1.0 - EH, 5);\r\n  #endif\r\n\r\n  #if defined(USE_COOK_TORRANCE) || def"
+"ined(USE_TORRANCE_SPARROW)\r\n\tfloat geo = 2.0 * NH * min(NE, NL);\r\n\tge"
+"o /= max(EH, geo);\r\n  #endif  \r\n\r\n  #if defined(USE_COOK_TORRANCE)\r"
+"\n\tfloat m = sqrt(2.0 / max(shininess, EPSILON));\r\n\r\n\tfloat m_sq = m "
+"* m;\r\n\tfloat NH_sq = NH * NH;\r\n\tfloat beckmann = exp((NH_sq - 1.0) / "
+"max(m_sq * NH_sq, EPSILON)) / max(4.0 * m_sq * NH_sq * NH_sq, EPSILON);\r\n"
+"\r\n\treturn fresnel * geo * beckmann / max(NE, EPSILON);\r\n  #endif\r\n\r"
+"\n  #if defined(USE_TRIACE)\r\n\tfloat scale = 0.1248582 * shininess + 0.26"
+"91817;\r\n\r\n\treturn fresnel * scale * blinn / max(max(NL, NE), EPSILON);"
+"\r\n  #endif\r\n  \r\n  #if defined(USE_TORRANCE_SPARROW)\r\n\tfloat scale "
+"= 0.125 * shininess + 1.0;\r\n\r\n\treturn fresnel * geo * scale * blinn / "
+"max(NE, EPSILON);\r\n  #endif\r\n}\r\n#endif\r\n\r\nvoid main()\r\n{\r\n#if"
+" defined(USE_LIGHT) || defined(USE_NORMALMAP)\r\n\tvec3 surfNormal = normal"
+"ize(var_Normal);\r\n#endif\r\n\r\n#if defined(USE_DELUXEMAP)\r\n\tvec3 worl"
+"dLight = 2.0 * texture2D(u_DeluxeMap, var_LightTex).xyz - vec3(1.0);\r\n\t/"
+"/worldLight += var_WorldLight * 0.0001;\r\n#elif defined(USE_LIGHT)\r\n\tve"
+"c3 worldLight = var_WorldLight;\r\n#endif\r\n\r\n#if defined(USE_LIGHTMAP)"
+"\r\n\tvec4 lightSample = texture2D(u_LightMap, var_LightTex).rgba;\r\n  #if"
+" defined(RGBE_LIGHTMAP)\r\n\tlightSample.rgb *= exp2(lightSample.a * 255.0 "
+"- 128.0);\r\n  #endif\r\n\tvec3 directedLight = lightSample.rgb;\r\n#elif d"
+"efined(USE_LIGHT_VECTOR)\r\n  #if defined(USE_FAST_LIGHT)\r\n\tvec3 directe"
+"dLight = var_VertLight;\r\n  #else\r\n    #if defined(USE_INVSQRLIGHT)\r\n"
+"\tfloat intensity = 1.0 / dot(worldLight, worldLight);\r\n    #else\r\n\tfl"
+"oat intensity = clamp((1.0 - dot(worldLight, worldLight) / (u_LightRadius *"
+" u_LightRadius)) * 1.07, 0.0, 1.0);\r\n    #endif\r\n\r\n\tvec3 directedLig"
+"ht = u_DirectedLight * intensity;\r\n\tvec3 ambientLight  = u_AmbientLight;"
+"\r\n  #endif\r\n\r\n  #if defined(USE_SHADOWMAP)\r\n\tvec2 shadowTex = var_"
+"ScreenPos.xy / var_ScreenPos.w * 0.5 + 0.5;\r\n\tdirectedLight *= texture2D"
+"(u_ShadowMap, shadowTex).r;\r\n  #endif\r\n#elif defined(USE_LIGHT_VERTEX)"
+"\r\n\tvec3 directedLight = var_VertLight;\r\n#endif\r\n\t\r\n#if defined(TC"
+"GEN_ENVIRONMENT) || defined(USE_NORMALMAP) || (defined(USE_LIGHT) && !defin"
+"ed(USE_FAST_LIGHT))\r\n\tvec3 SampleToView = normalize(var_SampleToView);\r"
+"\n#endif\r\n\tvec2 tex = var_DiffuseTex;\r\n\r\n\tfloat ambientDiff = 1.0;"
+"\r\n\r\n#if defined(USE_NORMALMAP)\r\n  #if defined(USE_VERT_TANGENT_SPACE)"
+"\r\n    vec3   tangent = var_Tangent;\r\n\tvec3 bitangent = var_Bitangent;"
+"\r\n  #else\r\n\tvec3 q0  = dFdx(var_Position);\r\n\tvec3 q1  = dFdy(var_Po"
+"sition);\r\n\tvec2 st0 = dFdx(tex);\r\n\tvec2 st1 = dFdy(tex);\r\n\tfloat d"
+"ir = sign(st1.t * st0.s - st0.t * st1.s);\r\n\r\n\tvec3   tangent = normali"
+"ze( q0 * st1.t - q1 * st0.t) * dir;\r\n\tvec3 bitangent = -normalize( q0 * "
+"st1.s - q1 * st0.s) * dir;\r\n  #endif\r\n\r\n\tmat3 tangentToWorld = mat3("
+"tangent, bitangent, var_Normal);\r\n\r\n  #if defined(USE_PARALLAXMAP)\r\n"
+"\tvec3 offsetDir = normalize(SampleToView * tangentToWorld);\r\n    #if 0\r"
+"\n    float height = SampleHeight(u_NormalMap, tex);\r\n\tfloat pdist = 0.0"
+"5 * height - (0.05 / 2.0);\r\n    #else\r\n\toffsetDir.xy *= -0.05 / offset"
+"Dir.z;\r\n\tfloat pdist = RayIntersectDisplaceMap(tex, offsetDir.xy, u_Norm"
+"alMap);\r\n    #endif\t\r\n\ttex += offsetDir.xy * pdist;\r\n  #endif\r\n  "
+"#if defined(SWIZZLE_NORMALMAP)\r\n\tvec3 normal = 2.0 * texture2D(u_NormalM"
+"ap, tex).agb - 1.0;\r\n  #else\r\n\tvec3 normal = 2.0 * texture2D(u_NormalM"
+"ap, tex).rgb - 1.0;\r\n  #endif\r\n\tnormal.z = sqrt(clamp(1.0 - dot(normal"
+".xy, normal.xy), 0.0, 1.0));\r\n\tvec3 worldNormal = tangentToWorld * norma"
+"l;\r\n  #if defined(r_normalAmbient)\r\n\tambientDiff = 0.781341 * normal.z"
+" + 0.218659;\r\n  #endif\r\n#elif defined(USE_LIGHT)\r\n\tvec3 worldNormal "
+"= surfNormal;\r\n#endif\r\n\r\n#if (defined(USE_LIGHT) && !defined(USE_FAST"
+"_LIGHT)) || (defined(TCGEN_ENVIRONMENT) && defined(USE_NORMALMAP))\r\n\twor"
+"ldNormal = normalize(worldNormal);\r\n#endif\r\n\r\n#if defined(TCGEN_ENVIR"
+"ONMENT) && defined(USE_NORMALMAP)\r\n\tvec3 reflected = worldNormal * 2.0 *"
+" dot(worldNormal, SampleToView) - SampleToView;\r\n\r\n\ttex = reflected.yz"
+" * vec2(0.5, -0.5) + 0.5;\r\n#endif\r\n\r\n\tvec4 diffuse = texture2D(u_Dif"
+"fuseMap, tex);\r\n\r\n#if defined(USE_LIGHT) && defined(USE_FAST_LIGHT)\r\n"
+"\tdiffuse.rgb *= directedLight;\r\n#elif defined(USE_LIGHT)\r\n\tworldLight"
+" = normalize(worldLight);\r\n\r\n  #if defined(USE_LIGHTMAP)\r\n\tdirectedL"
+"ight /= max(dot(surfNormal, worldLight), 0.004);\r\n  #endif\r\n\r\n  #if d"
+"efined(USE_LIGHTMAP) || defined(USE_LIGHT_VERTEX)\r\n\t#if defined(r_normal"
+"Ambient)\r\n\tvec3 ambientLight = directedLight * r_normalAmbient;\r\n\tdir"
+"ectedLight -= ambientLight;\r\n    #else\r\n\tvec3 ambientLight = vec3(0);"
+"\r\n    #endif\r\n  #endif\r\n\r\n\tfloat NL = clamp(dot(worldNormal,  worl"
+"dLight),   0.0, 1.0);\r\n\tfloat surfNL = clamp(dot(surfNormal,  worldLight"
+"),   0.0, 1.0);\r\n\tNL = min(NL, surfNL * 2.0);\r\n\tfloat NE = clamp(dot("
+"worldNormal,  SampleToView), 0.0, 1.0);\r\n\t\r\n\tfloat fzero = u_Material"
+"Info.x;\r\n\tfloat shininess = u_MaterialInfo.y;\r\n  #if defined(USE_SPECU"
+"LARMAP)\r\n\tvec4 specular = texture2D(u_SpecularMap, tex);\r\n\t//specular"
+".rgb = clamp(specular.rgb - diffuse.rgb, 0.0, 1.0);\r\n\tshininess *= specu"
+"lar.a;\r\n  #endif\r\n\tfloat directedDiff = NL * CalcDiffuse(worldNormal, "
+"worldLight, SampleToView, NE, NL, fzero, shininess);\r\n\tdiffuse.rgb *= di"
+"rectedLight * directedDiff + ambientDiff * ambientLight;\r\n  \r\n  #if def"
+"ined(USE_SPECULARMAP)\r\n\tvec3 halfAngle = normalize(worldLight + SampleTo"
+"View);\r\n\r\n\tfloat EH = clamp(dot(SampleToView, halfAngle), 0.0, 1.0);\r"
+"\n\tfloat NH = clamp(dot(worldNormal,  halfAngle), 0.0, 1.0);\r\n\r\n\tfloa"
+"t directedSpec = NL * CalcSpecular(NH, NL, NE, EH, fzero, shininess);\r\n  "
+"\r\n    #if defined(r_normalAmbient)\r\n\tvec3 ambientHalf = normalize(surf"
+"Normal + SampleToView);\r\n\tfloat ambientSpec = max(dot(ambientHalf, world"
+"Normal) + 0.5, 0.0);\r\n\tambientSpec *= ambientSpec * 0.44;\r\n\tambientSp"
+"ec = pow(ambientSpec, shininess) * fzero;\r\n\tspecular.rgb *= directedSpec"
+" * directedLight + ambientSpec * ambientLight;\r\n    #else\r\n\tspecular.r"
+"gb *= directedSpec * directedLight;\r\n    #endif\r\n  #endif\r\n#endif\r\n"
+"\r\n\tgl_FragColor = diffuse;\r\n\r\n#if defined(USE_SPECULARMAP) && define"
+"d(USE_LIGHT) && !defined(USE_FAST_LIGHT)\r\n\tgl_FragColor.rgb += specular."
+"rgb;\r\n#endif\r\n\r\n\tgl_FragColor *= var_Color;\r\n}\r\n";
+
+static const char *fallbackShadowfillShader_vp =
+"attribute vec4  attr_Position;\r\nattribute vec3  attr_Normal;\r\nattribute"
+" vec4  attr_TexCoord0;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nattrib"
+"ute vec4  attr_Position2;\r\nattribute vec3  attr_Normal2;\r\n//#endif\r\n"
+"\r\n//#if defined(USE_DEFORM_VERTEXES)\r\nuniform int     u_DeformGen;\r\nu"
+"niform float    u_DeformParams[5];\r\n//#endif\r\n\r\nuniform float   u_Tim"
+"e;\r\nuniform mat4    u_ModelViewProjectionMatrix;\r\n\r\nuniform mat4   u_"
+"ModelMatrix;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nuniform float   "
+"u_VertexLerp;\r\n//#endif\r\n\r\nvarying vec3    var_Position;\r\n\r\nfloat"
+" triangle(float x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\nfloat"
+" sawtooth(float x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 DeformPo"
+"sition(const vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif (u_De"
+"formGen == 0)\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base =      u"
+"_DeformParams[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat phas"
+"e =     u_DeformParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r\n\tf"
+"loat spread =    u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DGEN_WA"
+"VE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * sprea"
+"d;\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M"
+"_PI * 0.25 * st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * frequenc"
+"y);\r\n\tfloat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n"
+"\t\tfunc = sin(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen == DGE"
+"N_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n\t}\r"
+"\n\telse if (u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = triang"
+"le(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r"
+"\n\t\tfunc = sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE"
+"_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t}\r\n"
+"\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);\r\n"
+"\t}\r\n\r\n\tvec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base + fun"
+"c * amplitude);\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\n\r\nvoid main()\r"
+"\n{\r\n\tvec4 position = mix(attr_Position, attr_Position2, u_VertexLerp);"
+"\r\n\tvec3 normal = normalize(mix(attr_Normal, attr_Normal2, u_VertexLerp))"
+";\r\n\r\n\tposition = DeformPosition(position, normal, attr_TexCoord0.st);"
+"\r\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\t\r\n\t"
+"var_Position  = (u_ModelMatrix * position).xyz;\r\n}\r\n";
+
+static const char *fallbackShadowfillShader_fp =
+"uniform vec4  u_LightOrigin;\r\nuniform float u_LightRadius;\r\n\r\nvarying"
+" vec3  var_Position;\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_DEPTH)\r\n"
+"\tfloat depth = length(u_LightOrigin.xyz - var_Position) / u_LightRadius;\r"
+"\n #if 0\r\n\t// 32 bit precision\r\n\tconst vec4 bitSh = vec4( 256 * 256 *"
+" 256,   256 * 256,         256,           1);\r\n\tconst vec4 bitMsk = vec4"
+"(              0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\r\n\t\r\n\tvec4 c"
+"omp;\r\n\tcomp = depth * bitSh;\r\n\tcomp.xyz = fract(comp.xyz);\r\n\tcomp "
+"-= comp.xxyz * bitMsk;\r\n\tgl_FragColor = comp;\r\n #endif\r\n\r\n #if 1\r"
+"\n\t// 24 bit precision\r\n\tconst vec3 bitSh = vec3( 256 * 256,         25"
+"6,           1);\r\n\tconst vec3 bitMsk = vec3(        0, 1.0 / 256.0, 1.0 "
+"/ 256.0);\r\n\t\r\n\tvec3 comp;\r\n\tcomp = depth * bitSh;\r\n\tcomp.xy = f"
+"ract(comp.xy);\r\n\tcomp -= comp.xxy * bitMsk;\r\n\tgl_FragColor = vec4(com"
+"p, 1.0);\r\n #endif\r\n\r\n #if 0\r\n\t// 8 bit precision\r\n\tgl_FragColor"
+" = vec4(depth, depth, depth, 1);\r\n #endif\r\n#else\r\n\tgl_FragColor = ve"
+"c4(0, 0, 0, 1);\r\n#endif\r\n}\r\n";
+
+static const char *fallbackPshadowShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\n\r\nuniform"
+" mat4   u_ModelViewProjectionMatrix;\r\nvarying vec3   var_Position;\r\nvar"
+"ying vec3   var_Normal;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 position  = a"
+"ttr_Position;\r\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position"
+";\r\n\r\n\tvar_Position  = position.xyz;\r\n\tvar_Normal    = attr_Normal;"
+"\r\n}\r\n";
+
+static const char *fallbackPshadowShader_fp =
+"uniform sampler2D u_ShadowMap;\r\n\r\nuniform vec3      u_LightForward;\r\n"
+"uniform vec3      u_LightUp;\r\nuniform vec3      u_LightRight;\r\nuniform "
+"vec4      u_LightOrigin;\r\nuniform float     u_LightRadius;\r\nvarying vec"
+"3      var_Position;\r\nvarying vec3      var_Normal;\r\n\r\nfloat sampleDi"
+"stMap(sampler2D texMap, vec2 uv, float scale)\r\n{\r\n\tvec3 distv = textur"
+"e2D(texMap, uv).xyz;\r\n\treturn dot(distv, vec3(1.0 / (256.0 * 256.0), 1.0"
+" / 256.0, 1.0)) * scale;\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec3 lightToPos "
+"= var_Position - u_LightOrigin.xyz;\r\n\tvec2 st = vec2(-dot(u_LightRight, "
+"lightToPos), dot(u_LightUp, lightToPos));\r\n\t\r\n\tfloat fade = length(st"
+");\r\n\t\r\n#if defined(USE_DISCARD)\r\n\tif (fade >= 1.0)\r\n\t{\r\n\t\tdi"
+"scard;\r\n\t}\r\n#endif\r\n\r\n\tfade = clamp(8.0 - fade * 8.0, 0.0, 1.0);"
+"\r\n\t\r\n\tst = st * 0.5 + vec2(0.5);\r\n\r\n#if defined(USE_SOLID_PSHADOW"
+"S)\r\n\tfloat intensity = max(sign(u_LightRadius - length(lightToPos)), 0.0"
+");\r\n#else\r\n\tfloat intensity = clamp((1.0 - dot(lightToPos, lightToPos)"
+" / (u_LightRadius * u_LightRadius)) * 2.0, 0.0, 1.0);\r\n#endif\r\n\t\r\n\t"
+"float lightDist = length(lightToPos);\r\n\tfloat dist;\r\n\r\n#if defined(U"
+"SE_DISCARD)\r\n\tif (dot(u_LightForward, lightToPos) <= 0.0)\r\n\t{\r\n\t\t"
+"discard;\r\n\t}\r\n\r\n\tif (dot(var_Normal, lightToPos) > 0.0)\r\n\t{\r\n"
+"\t\tdiscard;\r\n\t}\r\n#else\r\n\tintensity *= max(sign(dot(u_LightForward,"
+" lightToPos)), 0.0);\r\n\tintensity *= max(sign(-dot(var_Normal, lightToPos"
+")), 0.0);\r\n#endif\r\n\r\n\tintensity *= fade;\r\n#if defined(USE_PCF)\r\n"
+"\tfloat part;\r\n\t\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/5"
+"12.0, -1.0/512.0), u_LightRadius);\r\n\tpart =  max(sign(lightDist - dist),"
+" 0.0);\r\n\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0, -1."
+"0/512.0), u_LightRadius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r"
+"\n\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/512.0,  1.0/512.0)"
+", u_LightRadius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r\n\r\n\td"
+"ist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0,  1.0/512.0), u_Light"
+"Radius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r\n\r\n  #if define"
+"d(USE_DISCARD)\r\n\tif (part <= 0.0)\r\n\t{\r\n\t\tdiscard;\r\n\t}\r\n  #en"
+"dif\r\n\r\n\tintensity *= part * 0.25;\r\n#else\r\n\tdist = sampleDistMap(u"
+"_ShadowMap, st, u_LightRadius);\r\n\r\n  #if defined(USE_DISCARD)\r\n\tif ("
+"lightDist - dist <= 0.0)\r\n\t{\r\n\t\tdiscard;\r\n\t}\r\n  #endif\r\n\t\t"
+"\t\r\n\tintensity *= max(sign(lightDist - dist), 0.0);\r\n#endif\r\n\t\t\r"
+"\n\tgl_FragColor.rgb = vec3(0);\r\n\tgl_FragColor.a = clamp(intensity, 0.0,"
+" 0.75);\r\n}\r\n";
+
+static const char *fallbackDown4xShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif"
+"orm mat4   u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2   var_TexCoords"
+";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri"
+"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n";
+
+static const char *fallbackDown4xShader_fp =
+"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec2      u_InvTexRes;\r\nva"
+"rying vec2      var_TexCoords;\r\n\r\nvoid main()\r\n{\r\n\tvec4 color;\r\n"
+"\tvec2 tc;\r\n\t\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(-1.5, -1.5); "
+" color  = texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRe"
+"s * vec2(-0.5, -1.5);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_"
+"TexCoords + u_InvTexRes * vec2( 0.5, -1.5);  color += texture2D(u_TextureMa"
+"p, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 1.5, -1.5);  color +="
+" texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * v"
+"ec2(-1.5, -0.5); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoo"
+"rds + u_InvTexRes * vec2(-0.5, -0.5); color += texture2D(u_TextureMap, tc);"
+"\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 0.5, -0.5); color += texture"
+"2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 1.5, -0"
+".5); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_"
+"InvTexRes * vec2(-1.5,  0.5); color += texture2D(u_TextureMap, tc);\r\n\ttc"
+" = var_TexCoords + u_InvTexRes * vec2(-0.5,  0.5); color += texture2D(u_Tex"
+"tureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 0.5,  0.5); col"
+"or += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * "
+"vec2( 1.5,  0.5); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_T"
+"exCoords + u_InvTexRes * vec2(-1.5,  1.5);  color += texture2D(u_TextureMap"
+", tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(-0.5,  1.5);  color += "
+"texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( "
+"0.5,  1.5);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords "
+"+ u_InvTexRes * vec2( 1.5,  1.5);  color += texture2D(u_TextureMap, tc);\r"
+"\n\t\r\n\tcolor *= 0.0625;\r\n\t\r\n\tgl_FragColor = color;\r\n}\r\n";
+
+static const char *fallbackBokehShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif"
+"orm mat4   u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2   var_TexCoords"
+";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri"
+"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n";
+
+static const char *fallbackBokehShader_fp =
+"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec4      u_Color;\r\n\r\nun"
+"iform vec2      u_InvTexRes;\r\nvarying vec2      var_TexCoords;\r\n\r\nvoi"
+"d main()\r\n{\r\n\tvec4 color;\r\n\tvec2 tc;\r\n\r\n#if 0\r\n\tfloat c[7] ="
+" float[7](1.0, 0.9659258263, 0.8660254038, 0.7071067812, 0.5, 0.2588190451,"
+" 0.0);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[0],  c[6]);  co"
+"lor =  texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *"
+" vec2(  c[1],  c[5]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_"
+"TexCoords + u_InvTexRes * vec2(  c[2],  c[4]);  color += texture2D(u_Textur"
+"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[3],  c[3]);  co"
+"lor += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *"
+" vec2(  c[4],  c[2]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_"
+"TexCoords + u_InvTexRes * vec2(  c[5],  c[1]);  color += texture2D(u_Textur"
+"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[6],  c[0]);  co"
+"lor += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexR"
+"es * vec2(  c[1], -c[5]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = "
+"var_TexCoords + u_InvTexRes * vec2(  c[2], -c[4]);  color += texture2D(u_Te"
+"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[3], -c[3]);"
+"  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexR"
+"es * vec2(  c[4], -c[2]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = "
+"var_TexCoords + u_InvTexRes * vec2(  c[5], -c[1]);  color += texture2D(u_Te"
+"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[6], -c[0]);"
+"  color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_Inv"
+"TexRes * vec2( -c[0],  c[6]);  color += texture2D(u_TextureMap, tc);\r\n\tt"
+"c = var_TexCoords + u_InvTexRes * vec2( -c[1],  c[5]);  color += texture2D("
+"u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2],  c["
+"4]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_Inv"
+"TexRes * vec2( -c[3],  c[3]);  color += texture2D(u_TextureMap, tc);\r\n\tt"
+"c = var_TexCoords + u_InvTexRes * vec2( -c[4],  c[2]);  color += texture2D("
+"u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[5],  c["
+"1]);  color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u"
+"_InvTexRes * vec2( -c[1], -c[5]);  color += texture2D(u_TextureMap, tc);\r"
+"\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[4]);  color += textu"
+"re2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[3]"
+", -c[3]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + "
+"u_InvTexRes * vec2( -c[4], -c[2]);  color += texture2D(u_TextureMap, tc);\r"
+"\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[5], -c[1]);  color += textu"
+"re2D(u_TextureMap, tc);\r\n\t\r\n\tgl_FragColor = color * 0.04166667 * u_Co"
+"lor;\r\n#endif\r\n\r\n\tfloat c[5] = float[5](1.0, 0.9238795325, 0.70710678"
+"12, 0.3826834324, 0.0);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  "
+"c[0],  c[4]);  color =  texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoord"
+"s + u_InvTexRes * vec2(  c[1],  c[3]);  color += texture2D(u_TextureMap, tc"
+");\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[2],  c[2]);  color += t"
+"exture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  "
+"c[3],  c[1]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoord"
+"s + u_InvTexRes * vec2(  c[4],  c[0]);  color += texture2D(u_TextureMap, tc"
+");\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[1], -c[3]);  color "
+"+= texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec"
+"2(  c[2], -c[2]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexC"
+"oords + u_InvTexRes * vec2(  c[3], -c[1]);  color += texture2D(u_TextureMap"
+", tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(  c[4], -c[0]);  color "
+"+= texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes *"
+" vec2( -c[0],  c[4]);  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_"
+"TexCoords + u_InvTexRes * vec2( -c[1],  c[3]);  color += texture2D(u_Textur"
+"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2],  c[2]);  co"
+"lor += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *"
+" vec2( -c[3],  c[1]);  color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = "
+"var_TexCoords + u_InvTexRes * vec2( -c[1], -c[3]);  color += texture2D(u_Te"
+"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[2]);"
+"  color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexR"
+"es * vec2( -c[3], -c[1]);  color += texture2D(u_TextureMap, tc);\r\n\t\r\n"
+"\tgl_FragColor = color * 0.0625 * u_Color;\r\n}\r\n";
+
+static const char *fallbackToneMapShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif"
+"orm mat4   u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2   var_TexCoords"
+";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri"
+"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n";
+
+static const char *fallbackToneMapShader_fp =
+"uniform sampler2D u_TextureMap;\r\nuniform sampler2D u_LevelsMap;\r\n\r\nun"
+"iform vec4      u_Color;\r\n\r\nuniform vec2      u_AutoExposureMinMax;\r\n"
+"uniform vec3      u_ToneMinAvgMaxLinear;\r\n\r\nvarying vec2      var_TexCo"
+"ords;\r\n\r\nconst vec3  LUMINANCE_VECTOR =   vec3(0.2125, 0.7154, 0.0721);"
+" //vec3(0.299, 0.587, 0.114);\r\n\r\nvec3 FilmicTonemap(vec3 x)\r\n{\r\n\tc"
+"onst float SS  = 0.22; // Shoulder Strength\r\n\tconst float LS  = 0.30; //"
+" Linear Strength\r\n\tconst float LA  = 0.10; // Linear Angle\r\n\tconst fl"
+"oat TS  = 0.20; // Toe Strength\r\n\tconst float TAN = 0.01; // Toe Angle N"
+"umerator\r\n\tconst float TAD = 0.30; // Toe Angle Denominator\r\n\t\r\n\tv"
+"ec3 SSxx = SS * x * x;\r\n\tvec3 LSx = LS * x;\r\n\tvec3 LALSx = LSx * LA;"
+"\r\n\t\r\n\treturn ((SSxx + LALSx + TS * TAN) / (SSxx + LSx + TS * TAD)) - "
+"TAN / TAD;\r\n\r\n\t//return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD))"
+" - TAN/TAD;\r\n\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec4 color = texture2D(u_"
+"TextureMap, var_TexCoords) * u_Color;\r\n\tvec3 minAvgMax = texture2D(u_Lev"
+"elsMap, var_TexCoords).rgb;\r\n\tvec3 logMinAvgMaxLum = clamp(minAvgMax * 2"
+"0.0 - 10.0, -u_AutoExposureMinMax.y, -u_AutoExposureMinMax.x);\r\n\t\t\r\n"
+"\tfloat avgLum = exp2(logMinAvgMaxLum.y);\r\n\t//float maxLum = exp2(logMin"
+"AvgMaxLum.z);\r\n\r\n\tcolor.rgb *= u_ToneMinAvgMaxLinear.y / avgLum;\r\n\t"
+"color.rgb = max(vec3(0.0), color.rgb - vec3(u_ToneMinAvgMaxLinear.x));\r\n"
+"\r\n\tvec3 fWhite = 1.0 / FilmicTonemap(vec3(u_ToneMinAvgMaxLinear.z - u_To"
+"neMinAvgMaxLinear.x));\r\n\tcolor.rgb = FilmicTonemap(color.rgb) * fWhite;"
+"\r\n\t\r\n\tgl_FragColor = clamp(color, 0.0, 1.0);\r\n}\r\n";
+
+static const char *fallbackCalcLevels4xShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif"
+"orm mat4   u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2   var_TexCoords"
+";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri"
+"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n";
+
+static const char *fallbackCalcLevels4xShader_fp =
+"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec4      u_Color;\r\n\r\nun"
+"iform vec2      u_InvTexRes;\r\nvarying vec2      var_TexCoords;\r\n\r\ncon"
+"st vec3  LUMINANCE_VECTOR =   vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0"
+".587, 0.114);\r\n\r\nvec3 GetValues(vec2 offset, vec3 current)\r\n{\r\n\tve"
+"c3 minAvgMax;\r\n\tvec2 tc = var_TexCoords + u_InvTexRes * offset; minAvgMa"
+"x = texture2D(u_TextureMap, tc).rgb;\r\n\r\n#ifdef FIRST_PASS\r\n\tfloat lu"
+"mi = max(dot(LUMINANCE_VECTOR, minAvgMax), 0.000001);\r\n\tfloat loglumi = "
+"clamp(log2(lumi), -10.0, 10.0);\r\n\tminAvgMax = vec3(loglumi * 0.05 + 0.5)"
+";\r\n#endif\r\n\r\n\treturn vec3(min(current.x, minAvgMax.x), current.y + m"
+"inAvgMax.y, max(current.z, minAvgMax.z));\r\n}\r\n\r\nvoid main()\r\n{\r\n"
+"\tvec3 current = vec3(1.0, 0.0, 0.0);\r\n\r\n#ifdef FIRST_PASS\r\n\tcurrent"
+" = GetValues(vec2( 0.0,  0.0), current);\r\n#else\r\n\tcurrent = GetValues("
+"vec2(-1.5, -1.5), current);\r\n\tcurrent = GetValues(vec2(-0.5, -1.5), curr"
+"ent);\r\n\tcurrent = GetValues(vec2( 0.5, -1.5), current);\r\n\tcurrent = G"
+"etValues(vec2( 1.5, -1.5), current);\r\n\t\r\n\tcurrent = GetValues(vec2(-1"
+".5, -0.5), current);\r\n\tcurrent = GetValues(vec2(-0.5, -0.5), current);\r"
+"\n\tcurrent = GetValues(vec2( 0.5, -0.5), current);\r\n\tcurrent = GetValue"
+"s(vec2( 1.5, -0.5), current);\r\n\t\r\n\tcurrent = GetValues(vec2(-1.5,  0."
+"5), current);\r\n\tcurrent = GetValues(vec2(-0.5,  0.5), current);\r\n\tcur"
+"rent = GetValues(vec2( 0.5,  0.5), current);\r\n\tcurrent = GetValues(vec2("
+" 1.5,  0.5), current);\r\n\r\n\tcurrent = GetValues(vec2(-1.5,  1.5), curre"
+"nt);\r\n\tcurrent = GetValues(vec2(-0.5,  1.5), current);\r\n\tcurrent = Ge"
+"tValues(vec2( 0.5,  1.5), current);\r\n\tcurrent = GetValues(vec2( 1.5,  1."
+"5), current);\r\n\r\n\tcurrent.y *= 0.0625;\r\n#endif\r\n\r\n\tgl_FragColor"
+" = vec4(current, 1.0f);\r\n}\r\n";
+
+static const char *fallbackShadowmaskShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif"
+"orm vec3   u_ViewForward;\r\nuniform vec3   u_ViewLeft;\r\nuniform vec3   u"
+"_ViewUp;\r\nuniform vec4   u_ViewInfo; // zfar / znear\r\n\r\nvarying vec2 "
+"  var_ScreenTex;\r\nvarying vec3   var_ViewDir;\r\n\r\nvoid main()\r\n{\r\n"
+"\tgl_Position = attr_Position;\r\n\t//vec2 screenCoords = gl_Position.xy / "
+"gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 + 0.5;\r\n\tvar_Sc"
+"reenTex = attr_TexCoord0.xy;\r\n\tvec2 screenCoords = attr_TexCoord0.xy * 2"
+".0 - 1.0;\r\n\tvar_ViewDir = u_ViewForward + u_ViewLeft * -screenCoords.x +"
+" u_ViewUp * screenCoords.y;\r\n}\r\n";
+
+static const char *fallbackShadowmaskShader_fp =
+"uniform sampler2D u_ScreenDepthMap;\r\n\r\nuniform sampler2D u_ShadowMap;\r"
+"\n#if defined(USE_SHADOW_CASCADE)\r\nuniform sampler2D u_ShadowMap2;\r\nuni"
+"form sampler2D u_ShadowMap3;\r\n#endif\r\n\r\nuniform mat4      u_ShadowMvp"
+";\r\n#if defined(USE_SHADOW_CASCADE)\r\nuniform mat4      u_ShadowMvp2;\r\n"
+"uniform mat4      u_ShadowMvp3;\r\n#endif\r\n\r\nuniform vec3   u_ViewOrigi"
+"n;\r\nuniform vec4   u_ViewInfo; // zfar / znear, zfar\r\n\r\nvarying vec2 "
+"  var_ScreenTex;\r\nvarying vec3   var_ViewDir;\r\n\r\n// Input: It uses te"
+"xture coords as the random number seed.\r\n// Output: Random number: [0,1),"
+" that is between 0.0 and 0.999999... inclusive.\r\n// Author: Michael Pohor"
+"eski\r\n// Copyright: Copyleft 2012 :-)\r\n// Source: http://stackoverflow."
+"com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader"
+"\r\n\r\nfloat random( const vec2 p )\r\n{\r\n  // We need irrationals for p"
+"seudo randomness.\r\n  // Most (all?) known transcendental numbers will (ge"
+"nerally) work.\r\n  const vec2 r = vec2(\r\n    23.1406926327792690,  // e^"
+"pi (Gelfond's constant)\r\n     2.6651441426902251); // 2^sqrt(2) (Gelfond–"
+"Schneider constant)\r\n  //return fract( cos( mod( 123456789., 1e-7 + 256. "
+"* dot(p,r) ) ) );\r\n  return mod( 123456789., 1e-7 + 256. * dot(p,r) );  "
+"\r\n}\r\n\r\nfloat PCF(const sampler2D shadowmap, const vec2 st, const floa"
+"t dist)\r\n{\r\n\tfloat mult;\r\n\tfloat scale = 2.0 / r_shadowMapSize;\r\n"
+"\t\t\r\n#if defined(USE_SHADOW_FILTER)\r\n\tfloat r = random(var_ScreenTex."
+"xy);\r\n\tfloat sinr = sin(r) * scale;\r\n\tfloat cosr = cos(r) * scale;\r"
+"\n\tmat2 rmat = mat2(cosr, sinr, -sinr, cosr);\r\n\r\n\tmult =  step(dist, "
+"texture2D(shadowmap, st + rmat * vec2(-0.7055767, 0.196515)).r);\r\n\tmult "
+"+= step(dist, texture2D(shadowmap, st + rmat * vec2(0.3524343, -0.7791386))"
+".r);\r\n\tmult += step(dist, texture2D(shadowmap, st + rmat * vec2(0.239105"
+"6, 0.9189604)).r);\r\n  #if defined(USE_SHADOW_FILTER2)\r\n\tmult += step(d"
+"ist, texture2D(shadowmap, st + rmat * vec2(-0.07580382, -0.09224417)).r);\r"
+"\n\tmult += step(dist, texture2D(shadowmap, st + rmat * vec2(0.5784913, -0."
+"002528916)).r);\r\n\tmult += step(dist, texture2D(shadowmap, st + rmat * ve"
+"c2(0.192888, 0.4064181)).r);\r\n\tmult += step(dist, texture2D(shadowmap, s"
+"t + rmat * vec2(-0.6335801, -0.5247476)).r);\r\n\tmult += step(dist, textur"
+"e2D(shadowmap, st + rmat * vec2(-0.5579782, 0.7491854)).r);\r\n\tmult += st"
+"ep(dist, texture2D(shadowmap, st + rmat * vec2(0.7320465, 0.6317794)).r);\r"
+"\n\r\n\tmult *= 0.11111;\r\n  #else\r\n    mult *= 0.33333;\r\n  #endif\r\n"
+"#else\r\n\tmult = step(dist, texture2D(shadowmap, st).r);\r\n#endif\r\n\t\t"
+"\r\n\treturn mult;\r\n}\r\n\r\nfloat getLinearDepth(sampler2D depthMap, vec"
+"2 tex, float zFarDivZNear)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthM"
+"ap, tex).r;\r\n\t\treturn 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r"
+"\n\r\nvoid main()\r\n{\r\n\tfloat result;\r\n\t\r\n\tfloat depth = getLinea"
+"rDepth(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x);\r\n\tfloat sampleZ ="
+" u_ViewInfo.y * depth;\r\n\r\n\tvec4 biasPos = vec4(u_ViewOrigin + var_View"
+"Dir * depth * 0.99, 1.0);\r\n\t\r\n\tvec4 shadowpos = u_ShadowMvp * biasPos"
+";\r\n\t\r\n#if defined(USE_SHADOW_CASCADE)\r\n\tconst float fadeTo = 0.5;\r"
+"\n\tresult = fadeTo;\r\n#else\r\n\tresult = 0.0;\r\n#endif\r\n\r\n\tif (all"
+"(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r\n\t{\r\n\t\t"
+"shadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r\n\t\tresult = PC"
+"F(u_ShadowMap, shadowpos.xy, shadowpos.z);\r\n\t}\r\n#if defined(USE_SHADOW"
+"_CASCADE)\r\n\telse\r\n\t{\r\n\t\tshadowpos = u_ShadowMvp2 * biasPos;\r\n\r"
+"\n\t\tif (all(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r"
+"\n\t\t{\r\n\t\t\tshadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r"
+"\n\t\t\tresult = PCF(u_ShadowMap2, shadowpos.xy, shadowpos.z);\r\n\t\t}\r\n"
+"\t\telse\r\n\t\t{\r\n\t\t\tshadowpos = u_ShadowMvp3 * biasPos;\r\n\r\n\t\t"
+"\tif (all(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r\n\t"
+"\t\t{\r\n\t\t\t\tshadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r"
+"\n\t\t\t\tresult = PCF(u_ShadowMap3, shadowpos.xy, shadowpos.z);\r\n\r\n\t"
+"\t\t\tfloat fade = clamp(sampleZ / r_shadowCascadeZFar * 10.0 - 9.0, 0.0, 1"
+".0);\r\n\t\t\t\tresult = mix(result, fadeTo, fade);\r\n\t\t\t}\r\n\t\t}\r\n"
+"\t}\r\n#endif\r\n\t\t\r\n\tgl_FragColor = vec4(vec3(result), 1.0);\r\n}\r\n";
+
+static const char *fallbackSsaoShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nvary"
+"ing vec2   var_ScreenTex;\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = attr_P"
+"osition;\r\n\tvar_ScreenTex = attr_TexCoord0.xy;\r\n\t//vec2 screenCoords ="
+" gl_Position.xy / gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 "
+"+ 0.5;\r\n}\r\n";
+
+static const char *fallbackSsaoShader_fp =
+"uniform sampler2D u_ScreenDepthMap;\r\n\r\nuniform vec4   u_ViewInfo; // zf"
+"ar / znear, zfar\r\n\r\nvarying vec2   var_ScreenTex;\r\n\r\nvec2 poissonDi"
+"sc[9] = vec2[9](\r\nvec2(-0.7055767, 0.196515),    vec2(0.3524343, -0.77913"
+"86),\r\nvec2(0.2391056, 0.9189604),    vec2(-0.07580382, -0.09224417),\r\nv"
+"ec2(0.5784913, -0.002528916), vec2(0.192888, 0.4064181),\r\nvec2(-0.6335801"
+", -0.5247476),  vec2(-0.5579782, 0.7491854),\r\nvec2(0.7320465, 0.6317794)"
+"\r\n);\r\n\r\n// Input: It uses texture coords as the random number seed.\r"
+"\n// Output: Random number: [0,1), that is between 0.0 and 0.999999... incl"
+"usive.\r\n// Author: Michael Pohoreski\r\n// Copyright: Copyleft 2012 :-)\r"
+"\n// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-ra"
+"ndom-number-inside-a-pixel-shader\r\n\r\nfloat random( const vec2 p )\r\n{"
+"\r\n  // We need irrationals for pseudo randomness.\r\n  // Most (all?) kno"
+"wn transcendental numbers will (generally) work.\r\n  const vec2 r = vec2("
+"\r\n    23.1406926327792690,  // e^pi (Gelfond's constant)\r\n     2.665144"
+"1426902251); // 2^sqrt(2) (Gelfond–Schneider constant)\r\n  //return fract("
+" cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) );\r\n  return mod( 12345"
+"6789., 1e-7 + 256. * dot(p,r) );  \r\n}\r\n\r\nmat2 randomRotation( const v"
+"ec2 p )\r\n{\r\n\tfloat r = random(p);\r\n\tfloat sinr = sin(r);\r\n\tfloat"
+" cosr = cos(r);\r\n\treturn mat2(cosr, sinr, -sinr, cosr);\r\n}\r\n\r\nfloa"
+"t getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNe"
+"ar)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthMap, tex).r;\r\n\t\tretu"
+"rn 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r\n\r\nfloat ambientOccl"
+"usion(sampler2D depthMap, const vec2 tex, const float zFarDivZNear, const f"
+"loat zFar)\r\n{\r\n\tfloat result = 0;\r\n\r\n\tfloat sampleZ = zFar * getL"
+"inearDepth(depthMap, tex, zFarDivZNear);\r\n\r\n\tvec2 expectedSlope = vec2"
+"(dFdx(sampleZ), dFdy(sampleZ)) / vec2(dFdx(tex.x), dFdy(tex.y));\r\n\t\r\n"
+"\tif (length(expectedSlope) > 5000.0)\r\n\t\treturn 1.0;\r\n\t\r\n\tvec2 of"
+"fsetScale = vec2(3.0 / sampleZ);\r\n\t\r\n\tmat2 rmat = randomRotation(tex)"
+";\r\n\t\t\r\n\tint i;\r\n\tfor (i = 0; i < 3; i++)\r\n\t{\r\n\t\tvec2 offse"
+"t = rmat * poissonDisc[i] * offsetScale;\r\n\t\tfloat sampleZ2 = zFar * get"
+"LinearDepth(depthMap, tex + offset, zFarDivZNear);\r\n\r\n\t\tif (abs(sampl"
+"eZ - sampleZ2) > 20.0)\r\n\t\t\tresult += 1.0;\r\n\t\telse\r\n\t\t{\r\n\t\t"
+"\tfloat expectedZ = sampleZ + dot(expectedSlope, offset);\r\n\t\t\tresult +"
+"= step(expectedZ - 1.0, sampleZ2);\r\n\t\t}\r\n\t}\r\n\t\r\n\tresult *= 0.3"
+"3333;\r\n\t\r\n\treturn result;\r\n}\r\n\r\nvoid main()\r\n{\r\n\tfloat res"
+"ult = ambientOcclusion(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_Vie"
+"wInfo.y);\r\n\t\t\t\r\n\tgl_FragColor = vec4(vec3(result), 1.0);\r\n}\r\n";
+
+static const char *fallbackDepthBlurShader_vp =
+"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nvary"
+"ing vec2   var_ScreenTex;\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = attr_P"
+"osition;\r\n\tvar_ScreenTex = attr_TexCoord0.xy;\r\n\t//vec2 screenCoords ="
+" gl_Position.xy / gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 "
+"+ 0.5;\r\n}\r\n";
+
+static const char *fallbackDepthBlurShader_fp =
+"uniform sampler2D u_ScreenImageMap;\r\nuniform sampler2D u_ScreenDepthMap;"
+"\r\n\r\nuniform vec4   u_ViewInfo; // zfar / znear, zfar\r\nvarying vec2   "
+"var_ScreenTex;\r\n\r\n//float gauss[5] = float[5](0.30, 0.23, 0.097, 0.024,"
+" 0.0033);\r\nfloat gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044);\r\n//flo"
+"at gauss[3] = float[3](0.60, 0.19, 0.0066);\r\n#define GAUSS_SIZE 4\r\n\r\n"
+"float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDi"
+"vZNear)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthMap, tex).r;\r\n\t\t"
+"return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r\n\r\nvec4 depthGau"
+"ssian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFarDivZNea"
+"r, float zFar)\r\n{\r\n\tfloat scale = 1.0 / 256.0;\r\n\r\n#if defined(USE_"
+"HORIZONTAL_BLUR)\r\n    vec2 direction = vec2(1.0, 0.0) * scale;\r\n#else /"
+"/ if defined(USE_VERTICAL_BLUR)\r\n\tvec2 direction = vec2(0.0, 1.0) * scal"
+"e;\r\n#endif\r\n\t\r\n\tfloat depthCenter = zFar * getLinearDepth(depthMap,"
+" tex, zFarDivZNear);\r\n\tvec2 centerSlope = vec2(dFdx(depthCenter), dFdy(d"
+"epthCenter)) / vec2(dFdx(tex.x), dFdy(tex.y));\r\n\t\t\r\n\tvec4 result = t"
+"exture2D(imageMap, tex) * gauss[0];\r\n\tfloat total = gauss[0];\r\n\r\n\ti"
+"nt i, j;\r\n\tfor (i = 0; i < 2; i++)\r\n\t{\r\n\t\tfor (j = 1; j < GAUSS_S"
+"IZE; j++)\r\n\t\t{\r\n\t\t\tvec2 offset = direction * j;\r\n\t\t\tfloat dep"
+"thSample = zFar * getLinearDepth(depthMap, tex + offset, zFarDivZNear);\r\n"
+"\t\t\tfloat depthExpected = depthCenter + dot(centerSlope, offset);\r\n\t\t"
+"\tif(abs(depthSample - depthExpected) < 5.0)\r\n\t\t\t{\r\n\t\t\t\tresult +"
+"= texture2D(imageMap, tex + offset) * gauss[j];\r\n\t\t\t\ttotal += gauss[j"
+"];\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tdirection = -direction;\r\n\t}\t\r\n"
+"\t\t\r\n\treturn result / total;\r\n}\r\n\r\nvoid main()\r\n{\t\t\r\n\tgl_F"
+"ragColor = depthGaussian1D(u_ScreenImageMap, u_ScreenDepthMap, var_ScreenTe"
+"x, u_ViewInfo.x, u_ViewInfo.y);\r\n}\r\n";
+
+
+static void GLSL_PrintInfoLog(GLhandleARB object, qboolean developerOnly)
+{
+	char           *msg;
+	static char     msgPart[1024];
+	int             maxLength = 0;
+	int             i;
+	int             printLevel = developerOnly ? PRINT_DEVELOPER : PRINT_ALL;
+
+	qglGetObjectParameterivARB(object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength);
+
+	if (maxLength <= 0)
+	{
+		ri.Printf(printLevel, "No compile log.\n");
+		return;
+	}
+
+	ri.Printf(printLevel, "compile log:\n");
+
+	if (maxLength < 1023)
+	{
+		qglGetInfoLogARB(object, maxLength, &maxLength, msgPart);
+
+		msgPart[maxLength + 1] = '\0';
+
+		ri.Printf(printLevel, "%s\n", msgPart);
+	}
+	else
+	{
+		msg = ri.Malloc(maxLength);
+
+		qglGetInfoLogARB(object, maxLength, &maxLength, msg);
+
+		for(i = 0; i < maxLength; i += 1024)
+		{
+			Q_strncpyz(msgPart, msg + i, sizeof(msgPart));
+
+			ri.Printf(printLevel, "%s\n", msgPart);
+		}
+
+		ri.Free(msg);
+	}
+}
+
+static void GLSL_PrintShaderSource(GLhandleARB object)
+{
+	char           *msg;
+	static char     msgPart[1024];
+	int             maxLength = 0;
+	int             i;
+
+	qglGetObjectParameterivARB(object, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &maxLength);
+
+	msg = ri.Malloc(maxLength);
+
+	qglGetShaderSourceARB(object, maxLength, &maxLength, msg);
+
+	for(i = 0; i < maxLength; i += 1024)
+	{
+		Q_strncpyz(msgPart, msg + i, sizeof(msgPart));
+		ri.Printf(PRINT_ALL, "%s\n", msgPart);
+	}
+
+	ri.Free(msg);
+}
+
+static void GLSL_GetShaderHeader( GLenum shaderType, const GLcharARB *extra, char *dest, int size )
+{
+	float fbufWidthScale, fbufHeightScale;
+
+	dest[0] = '\0';
+
+	// HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones
+	if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30))
+	{
+		Q_strcat(dest, size, "#version 130\n");
+
+		if(shaderType == GL_VERTEX_SHADER_ARB)
+		{
+			Q_strcat(dest, size, "#define attribute in\n");
+			Q_strcat(dest, size, "#define varying out\n");
+		}
+		else
+		{
+			Q_strcat(dest, size, "#define varying in\n");
+
+			Q_strcat(dest, size, "out vec4 out_Color;\n");
+			Q_strcat(dest, size, "#define gl_FragColor out_Color\n");
+		}
+	}
+	else
+	{
+		Q_strcat(dest, size, "#version 120\n");
+	}
+
+	// HACK: add some macros to avoid extra uniforms and save speed and code maintenance
+	//Q_strcat(dest, size,
+	//		 va("#ifndef r_SpecularExponent\n#define r_SpecularExponent %f\n#endif\n", r_specularExponent->value));
+	//Q_strcat(dest, size,
+	//		 va("#ifndef r_SpecularScale\n#define r_SpecularScale %f\n#endif\n", r_specularScale->value));
+	//Q_strcat(dest, size,
+	//       va("#ifndef r_NormalScale\n#define r_NormalScale %f\n#endif\n", r_normalScale->value));
+
+
+	Q_strcat(dest, size, "#ifndef M_PI\n#define M_PI 3.14159265358979323846f\n#endif\n");
+
+	//Q_strcat(dest, size, va("#ifndef MAX_SHADOWMAPS\n#define MAX_SHADOWMAPS %i\n#endif\n", MAX_SHADOWMAPS));
+
+	Q_strcat(dest, size,
+					 va("#ifndef deformGen_t\n"
+						"#define deformGen_t\n"
+						"#define DGEN_WAVE_SIN %i\n"
+						"#define DGEN_WAVE_SQUARE %i\n"
+						"#define DGEN_WAVE_TRIANGLE %i\n"
+						"#define DGEN_WAVE_SAWTOOTH %i\n"
+						"#define DGEN_WAVE_INVERSE_SAWTOOTH %i\n"
+						"#define DGEN_BULGE %i\n"
+						"#define DGEN_MOVE %i\n"
+						"#endif\n",
+						DGEN_WAVE_SIN,
+						DGEN_WAVE_SQUARE,
+						DGEN_WAVE_TRIANGLE,
+						DGEN_WAVE_SAWTOOTH,
+						DGEN_WAVE_INVERSE_SAWTOOTH,
+						DGEN_BULGE,
+						DGEN_MOVE));
+
+	Q_strcat(dest, size,
+					 va("#ifndef tcGen_t\n"
+						"#define tcGen_t\n"
+						"#define TCGEN_LIGHTMAP %i\n"
+						"#define TCGEN_TEXTURE %i\n"
+						"#define TCGEN_ENVIRONMENT_MAPPED %i\n"
+						"#define TCGEN_FOG %i\n"
+						"#define TCGEN_VECTOR %i\n"
+						"#endif\n",
+						TCGEN_LIGHTMAP,
+						TCGEN_TEXTURE,
+						TCGEN_ENVIRONMENT_MAPPED,
+						TCGEN_FOG,
+						TCGEN_VECTOR));
+
+	Q_strcat(dest, size,
+					 va("#ifndef colorGen_t\n"
+						"#define colorGen_t\n"
+						"#define CGEN_LIGHTING_DIFFUSE %i\n"
+						"#endif\n",
+						CGEN_LIGHTING_DIFFUSE));
+
+	Q_strcat(dest, size,
+							 va("#ifndef alphaGen_t\n"
+								"#define alphaGen_t\n"
+								"#define AGEN_LIGHTING_SPECULAR %i\n"
+								"#define AGEN_PORTAL %i\n"
+								"#define AGEN_FRESNEL %i\n"
+								"#endif\n",
+								AGEN_LIGHTING_SPECULAR,
+								AGEN_PORTAL,
+								AGEN_FRESNEL));
+
+	Q_strcat(dest, size,
+							 va("#ifndef texenv_t\n"
+								"#define texenv_t\n"
+								"#define TEXENV_MODULATE %i\n"
+								"#define TEXENV_ADD %i\n"
+								"#define TEXENV_REPLACE %i\n"
+								"#endif\n",
+								GL_MODULATE,
+								GL_ADD,
+								GL_REPLACE));
+
+	fbufWidthScale = 1.0f / ((float)glConfig.vidWidth);
+	fbufHeightScale = 1.0f / ((float)glConfig.vidHeight);
+	Q_strcat(dest, size,
+			 va("#ifndef r_FBufScale\n#define r_FBufScale vec2(%f, %f)\n#endif\n", fbufWidthScale, fbufHeightScale));
+
+	if (extra)
+	{
+		Q_strcat(dest, size, extra);
+	}
+
+	// OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line
+	// so we have to reset the line counting
+	Q_strcat(dest, size, "#line 0\n");
+}
+
+static int GLSL_CompileGPUShader(GLhandleARB program, GLhandleARB *prevShader, const GLcharARB *buffer, int size, GLenum shaderType)
+{
+	GLint           compiled;
+	GLhandleARB     shader;
+
+	shader = qglCreateShaderObjectARB(shaderType);
+
+	qglShaderSourceARB(shader, 1, (const GLcharARB **)&buffer, &size);
+
+	// compile shader
+	qglCompileShaderARB(shader);
+
+	// check if shader compiled
+	qglGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &compiled);
+	if(!compiled)
+	{
+		GLSL_PrintShaderSource(shader);
+		GLSL_PrintInfoLog(shader, qfalse);
+		ri.Error(ERR_DROP, "Couldn't compile shader");
+		return 0;
+	}
+
+	//GLSL_PrintInfoLog(shader, qtrue);
+	//GLSL_PrintShaderSource(shader);
+
+	if (*prevShader)
+	{
+		qglDetachObjectARB(program, *prevShader);
+		qglDeleteObjectARB(*prevShader);
+	}
+
+	// attach shader to program
+	qglAttachObjectARB(program, shader);
+
+	*prevShader = shader;
+
+	return 1;
+}
+
+
+static void GLSL_DumpText(const char *shaderText, int size, const char *name, GLenum shaderType)
+{
+	int i, l, inc;
+	ri.Printf(PRINT_ALL, "static const char *fallback%sShader_%s =\n\"", name, shaderType == GL_VERTEX_SHADER_ARB ? "vp" : "fp");
+	l = 0;
+
+	for (i = 0; i < size; i++)
+	{
+		switch (shaderText[i])
+		{
+			case '\a':
+			case '\b':
+			case '\f':
+			case '\n':
+			case '\r':
+			case '\t':
+			case '\v':
+			case '"':
+			case '\\':
+				inc = 2;
+				break;
+			default:
+				inc = 1;
+				break;
+		}
+
+		l += inc;
+
+		if (l >= 76)
+		{
+			ri.Printf(PRINT_ALL, "\"\n\"");
+			l = inc;
+		}
+
+		switch (shaderText[i])
+		{
+			case '\a':
+				ri.Printf(PRINT_ALL, "\\a");
+				break;
+			case '\b':
+				ri.Printf(PRINT_ALL, "\\b");
+				break;
+			case '\f':
+				ri.Printf(PRINT_ALL, "\\f");
+				break;
+			case '\n':
+				ri.Printf(PRINT_ALL, "\\n");
+				break;
+			case '\r':
+				ri.Printf(PRINT_ALL, "\\r");
+				break;
+			case '\t':
+				ri.Printf(PRINT_ALL, "\\t");
+				break;
+			case '\v':
+				ri.Printf(PRINT_ALL, "\\v");
+				break;
+			case '"':
+				ri.Printf(PRINT_ALL, "\\\"");
+				break;
+			case '\\':
+				ri.Printf(PRINT_ALL, "\\\\");
+				break;
+			default:
+				ri.Printf(PRINT_ALL, "%c", shaderText[i]);
+				break;
+		}
+	}
+	ri.Printf(PRINT_ALL, "\";\n\n");
+}
+
+static int GLSL_LoadGPUShaderText(const char *name, const char *fallback,
+	GLenum shaderType, char *dest, int destSize, qboolean dump)
+{
+	char            filename[MAX_QPATH];
+	GLcharARB      *buffer = NULL;
+	const GLcharARB *shaderText = NULL;
+	int             size;
+	int             result;
+
+	if(shaderType == GL_VERTEX_SHADER_ARB)
+	{
+		Com_sprintf(filename, sizeof(filename), "glsl/%s_vp.glsl", name);
+	}
+	else
+	{
+		Com_sprintf(filename, sizeof(filename), "glsl/%s_fp.glsl", name);
+	}
+
+	ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename);
+	size = ri.FS_ReadFile(filename, (void **)&buffer);
+	if(!buffer)
+	{
+		if (fallback)
+		{
+			ri.Printf(PRINT_DEVELOPER, "couldn't load, using fallback\n");
+			shaderText = fallback;
+			size = strlen(shaderText);
+		}
+		else
+		{
+			ri.Printf(PRINT_DEVELOPER, "couldn't load!\n");
+			return 0;
+		}
+	}
+	else
+	{
+		shaderText = buffer;
+	}
+
+	if (dump)
+		GLSL_DumpText(shaderText, size, name, shaderType);
+
+	if (size > destSize)
+	{
+		result = 0;
+	}
+	else
+	{
+		Q_strncpyz(dest, shaderText, size + 1);
+		result = 1;
+	}
+
+	if (buffer)
+	{
+		ri.FS_FreeFile(buffer);
+	}
+	
+	return result;
+}
+
+static void GLSL_LinkProgram(GLhandleARB program)
+{
+	GLint           linked;
+
+	qglLinkProgramARB(program);
+
+	qglGetObjectParameterivARB(program, GL_OBJECT_LINK_STATUS_ARB, &linked);
+	if(!linked)
+	{
+		GLSL_PrintInfoLog(program, qfalse);
+		ri.Error(ERR_DROP, "\nshaders failed to link");
+	}
+}
+
+static void GLSL_ValidateProgram(GLhandleARB program)
+{
+	GLint           validated;
+
+	qglValidateProgramARB(program);
+
+	qglGetObjectParameterivARB(program, GL_OBJECT_VALIDATE_STATUS_ARB, &validated);
+	if(!validated)
+	{
+		GLSL_PrintInfoLog(program, qfalse);
+		ri.Error(ERR_DROP, "\nshaders failed to validate");
+	}
+}
+
+static void GLSL_ShowProgramUniforms(GLhandleARB program)
+{
+	int             i, count, size;
+	GLenum			type;
+	char            uniformName[1000];
+
+	// install the executables in the program object as part of current state.
+	qglUseProgramObjectARB(program);
+
+	// check for GL Errors
+
+	// query the number of active uniforms
+	qglGetObjectParameterivARB(program, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count);
+
+	// Loop over each of the active uniforms, and set their value
+	for(i = 0; i < count; i++)
+	{
+		qglGetActiveUniformARB(program, i, sizeof(uniformName), NULL, &size, &type, uniformName);
+
+		ri.Printf(PRINT_DEVELOPER, "active uniform: '%s'\n", uniformName);
+	}
+
+	qglUseProgramObjectARB(0);
+}
+
+static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int attribs, const char *vpCode, const char *fpCode, int numUniforms)
+{
+	ri.Printf(PRINT_DEVELOPER, "------- GPU shader -------\n");
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "GLSL_InitGPUShader2: \"%s\" is too long\n", name);
+	}
+
+	Q_strncpyz(program->name, name, sizeof(program->name));
+
+	program->program = qglCreateProgramObjectARB();
+	program->attribs = attribs;
+
+	if (!(GLSL_CompileGPUShader(program->program, &program->vertexShader, vpCode, strlen(vpCode), GL_VERTEX_SHADER_ARB)))
+	{
+		ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_VERTEX_SHADER_ARB\n", name);
+		qglDeleteObjectARB(program->program);
+		return 0;
+	}
+
+	if(fpCode)
+	{
+		if(!(GLSL_CompileGPUShader(program->program, &program->fragmentShader, fpCode, strlen(fpCode), GL_FRAGMENT_SHADER_ARB)))
+		{
+			ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_FRAGMENT_SHADER_ARB\n", name);
+			qglDeleteObjectARB(program->program);
+			return 0;
+		}
+	}
+
+	if(attribs & ATTR_POSITION)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION, "attr_Position");
+
+	if(attribs & ATTR_TEXCOORD)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0");
+
+	if(attribs & ATTR_LIGHTCOORD)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1");
+
+//  if(attribs & ATTR_TEXCOORD2)
+//      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2");
+
+//  if(attribs & ATTR_TEXCOORD3)
+//      qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3");
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if(attribs & ATTR_TANGENT)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT, "attr_Tangent");
+
+	if(attribs & ATTR_BITANGENT)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_BITANGENT, "attr_Bitangent");
+#endif
+
+	if(attribs & ATTR_NORMAL)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL, "attr_Normal");
+
+	if(attribs & ATTR_COLOR)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_COLOR, "attr_Color");
+
+	if(attribs & ATTR_PAINTCOLOR)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_PAINTCOLOR, "attr_PaintColor");
+
+	if(attribs & ATTR_LIGHTDIRECTION)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_LIGHTDIRECTION, "attr_LightDirection");
+
+	if(attribs & ATTR_POSITION2)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION2, "attr_Position2");
+
+	if(attribs & ATTR_NORMAL2)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2");
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if(attribs & ATTR_TANGENT2)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2");
+
+	if(attribs & ATTR_BITANGENT2)
+		qglBindAttribLocationARB(program->program, ATTR_INDEX_BITANGENT2, "attr_Bitangent2");
+#endif
+
+	GLSL_LinkProgram(program->program);
+
+	program->numUniforms = numUniforms;
+
+	{
+		int i, size;
+
+		size = sizeof(*program->uniforms) * numUniforms;
+		program->uniforms = ri.Malloc(size);
+		for (i = 0; i < numUniforms; i++)
+		{
+			program->uniforms[i] = -1;
+		}
+
+		size = sizeof(*program->uniformTypes) * numUniforms;
+		program->uniformTypes = ri.Malloc(size);
+		memset(program->uniformTypes, 0, size);
+
+		size = sizeof(*program->uniformBufferOffsets) * numUniforms;
+		program->uniformBufferOffsets = ri.Malloc(size);
+		memset(program->uniformBufferOffsets, 0, size);
+	}
+
+	return 1;
+}
+
+static int GLSL_InitGPUShader(shaderProgram_t * program, const char *name,
+	int attribs, qboolean fragmentShader, const GLcharARB *extra, qboolean addHeader,
+	const char *fallback_vp, const char *fallback_fp, int numUniforms)
+{
+	char vpCode[32000];
+	char fpCode[32000];
+	char *postHeader;
+	int size;
+	int result;
+
+	size = sizeof(vpCode);
+	if (addHeader)
+	{
+		GLSL_GetShaderHeader(GL_VERTEX_SHADER_ARB, extra, vpCode, size);
+		postHeader = &vpCode[strlen(vpCode)];
+		size -= strlen(vpCode);
+	}
+	else
+	{
+		postHeader = &vpCode[0];
+	}
+
+	if (!GLSL_LoadGPUShaderText(name, fallback_vp, GL_VERTEX_SHADER_ARB, postHeader, size, qfalse))
+	{
+		return 0;
+	}
+
+	if (fragmentShader)
+	{
+		size = sizeof(fpCode);
+		if (addHeader)
+		{
+			GLSL_GetShaderHeader(GL_FRAGMENT_SHADER_ARB, extra, fpCode, size);
+			postHeader = &fpCode[strlen(fpCode)];
+			size -= strlen(fpCode);
+		}
+		else
+		{
+			postHeader = &fpCode[0];
+		}
+
+		if (!GLSL_LoadGPUShaderText(name, fallback_fp, GL_FRAGMENT_SHADER_ARB, postHeader, size, qfalse))
+		{
+			return 0;
+		}
+	}
+
+	result = GLSL_InitGPUShader2(program, name, attribs, vpCode, fragmentShader ? fpCode : NULL, numUniforms);
+
+	return result;
+}
+
+// intentionally deceiving the user here, not actually setting the names but getting their indexes.
+void GLSL_AddUniform(shaderProgram_t *program, int uniformNum, const char *name, int type)
+{
+	GLint *uniforms = program->uniforms;
+
+	uniforms[uniformNum] = qglGetUniformLocationARB(program->program, name);
+	program->uniformTypes[uniformNum] = type;
+}
+
+void GLSL_EndUniforms(shaderProgram_t *program)
+{
+	if (program->numUniforms)
+	{
+		int i, size;
+		 
+		size = 0;
+		for (i = 0; i < program->numUniforms; i++)
+		{
+			if (program->uniforms[i] != -1)
+			{
+				program->uniformBufferOffsets[i] = size;
+
+				switch(program->uniformTypes[i])
+				{
+					case GLSL_INT:
+						size += sizeof(GLint);
+						break;
+					case GLSL_FLOAT:
+						size += sizeof(GLfloat);
+						break;
+					case GLSL_FLOAT5:
+						size += sizeof(vec_t) * 5;
+						break;
+					case GLSL_VEC2:
+						size += sizeof(vec_t) * 2;
+						break;
+					case GLSL_VEC3:
+						size += sizeof(vec_t) * 3;
+						break;
+					case GLSL_VEC4:
+						size += sizeof(vec_t) * 4;
+						break;
+					case GLSL_MAT16:
+						size += sizeof(vec_t) * 16;
+						break;
+					default:
+						break;
+				}
+			}
+		}
+
+		program->uniformBuffer = ri.Malloc(size);
+
+	}
+}
+
+void GLSL_FinishGPUShader(shaderProgram_t *program)
+{
+	GLSL_ValidateProgram(program->program);
+	GLSL_ShowProgramUniforms(program->program);
+	GL_CheckErrors();
+}
+
+void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value)
+{
+	GLint *uniforms = program->uniforms;
+	GLint *compare = (GLint *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_INT)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformInt: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (value == *compare)
+	{
+		return;
+	}
+
+	*compare = value;
+
+	qglUniform1iARB(uniforms[uniformNum], value);
+}
+
+void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value)
+{
+	GLint *uniforms = program->uniforms;
+	GLfloat *compare = (GLfloat *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_FLOAT)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (value == *compare)
+	{
+		return;
+	}
+
+	*compare = value;
+	
+	qglUniform1fARB(uniforms[uniformNum], value);
+}
+
+void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v)
+{
+	GLint *uniforms = program->uniforms;
+	vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_VEC2)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec2: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (v[0] == compare[0] && v[1] == compare[1])
+	{
+		return;
+	}
+
+	compare[0] = v[0];
+	compare[1] = v[1];
+
+	qglUniform2fARB(uniforms[uniformNum], v[0], v[1]);
+}
+
+void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v)
+{
+	GLint *uniforms = program->uniforms;
+	vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_VEC3)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec3: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (VectorCompare(v, compare))
+	{
+		return;
+	}
+
+	VectorCopy(v, compare);
+
+	qglUniform3fARB(uniforms[uniformNum], v[0], v[1], v[2]);
+}
+
+void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v)
+{
+	GLint *uniforms = program->uniforms;
+	vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_VEC4)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec4: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (VectorCompare4(v, compare))
+	{
+		return;
+	}
+
+	VectorCopy4(v, compare);
+
+	qglUniform4fARB(uniforms[uniformNum], v[0], v[1], v[2], v[3]);
+}
+
+void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v)
+{
+	GLint *uniforms = program->uniforms;
+	vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+	{
+		ri.Printf( PRINT_ALL, "well shit.\n");
+		return;
+	}
+
+	if (program->uniformTypes[uniformNum] != GLSL_FLOAT5)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat5: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (VectorCompare5(v, compare))
+	{
+		return;
+	}
+
+	VectorCopy5(v, compare);
+
+	qglUniform1fvARB(uniforms[uniformNum], 5, v);
+}
+
+void GLSL_SetUniformMatrix16(shaderProgram_t *program, int uniformNum, const matrix_t matrix)
+{
+	GLint *uniforms = program->uniforms;
+	vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+	if (uniforms[uniformNum] == -1)
+		return;
+
+	if (program->uniformTypes[uniformNum] != GLSL_MAT16)
+	{
+		ri.Printf( PRINT_WARNING, "GLSL_SetUniformMatrix16: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+		return;
+	}
+
+	if (Matrix16Compare(matrix, compare))
+	{
+		return;
+	}
+
+	Matrix16Copy(matrix, compare);
+
+	qglUniformMatrix4fvARB(uniforms[uniformNum], 1, GL_FALSE, matrix);
+}
+
+void GLSL_DeleteGPUShader(shaderProgram_t *program)
+{
+	if(program->program)
+	{
+		if (program->vertexShader)
+		{
+			qglDetachObjectARB(program->program, program->vertexShader);
+			qglDeleteObjectARB(program->vertexShader);
+		}
+
+		if (program->fragmentShader)
+		{
+			qglDetachObjectARB(program->program, program->fragmentShader);
+			qglDeleteObjectARB(program->fragmentShader);
+		}
+
+		qglDeleteObjectARB(program->program);
+
+		if (program->uniforms)
+		{
+			ri.Free(program->uniforms);
+		}
+
+		if (program->uniformTypes)
+		{
+			ri.Free(program->uniformTypes);
+		}
+				
+		if (program->uniformBuffer)
+		{
+			ri.Free(program->uniformBuffer);
+		}
+
+		if (program->uniformBufferOffsets)
+		{
+			ri.Free(program->uniformBufferOffsets);
+		}
+
+		Com_Memset(program, 0, sizeof(*program));
+	}
+}
+
+void GLSL_InitGPUShaders(void)
+{
+	int             startTime, endTime;
+	int i;
+	char extradefines[1024];
+	int attribs;
+	int numGenShaders = 0, numLightShaders = 0, numEtcShaders = 0;
+
+	ri.Printf(PRINT_ALL, "------- GLSL_InitGPUShaders -------\n");
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	startTime = ri.Milliseconds();
+
+	for (i = 0; i < GENERICDEF_COUNT; i++)
+	{	
+		attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_NORMAL | ATTR_COLOR;
+		extradefines[0] = '\0';
+
+		if (i & GENERICDEF_USE_DEFORM_VERTEXES)
+			Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n");
+
+		if (i & GENERICDEF_USE_TCGEN)
+			Q_strcat(extradefines, 1024, "#define USE_TCGEN\n");
+
+		if (i & GENERICDEF_USE_VERTEX_ANIMATION)
+		{
+			Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
+			attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
+		}
+
+		if (i & GENERICDEF_USE_FOG)
+			Q_strcat(extradefines, 1024, "#define USE_FOG\n");
+
+		if (i & GENERICDEF_USE_RGBAGEN)
+			Q_strcat(extradefines, 1024, "#define USE_RGBAGEN\n");
+
+		if (i & GENERICDEF_USE_LIGHTMAP)
+			Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n");
+
+		if (r_hdr->integer && !(glRefConfig.textureFloat && glRefConfig.halfFloatPixel))
+			Q_strcat(extradefines, 1024, "#define RGBE_LIGHTMAP\n");
+
+		if (!GLSL_InitGPUShader(&tr.genericShader[i], "generic", attribs, qtrue, extradefines, qtrue, fallbackGenericShader_vp, fallbackGenericShader_fp, GENERIC_UNIFORM_COUNT))
+		{
+			ri.Error(ERR_FATAL, "Could not load generic shader!\n");
+		}
+
+		// There's actually no need to filter these out, since they'll
+		// redirect to -1 if nonexistent, but it's more understandable this way.
+		
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_BASECOLOR, "u_BaseColor", GLSL_VEC4);
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VERTCOLOR, "u_VertColor", GLSL_VEC4);
+
+		if (i & GENERICDEF_USE_RGBAGEN)
+		{
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_COLORGEN,      "u_ColorGen",      GLSL_INT);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_ALPHAGEN,      "u_AlphaGen",      GLSL_INT);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_AMBIENTLIGHT,  "u_AmbientLight",  GLSL_VEC3);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIRECTEDLIGHT, "u_DirectedLight", GLSL_VEC3);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTORIGIN,   "u_LightOrigin",   GLSL_VEC4);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_PORTALRANGE,   "u_PortalRange",   GLSL_FLOAT);
+		}
+
+		if (i & GENERICDEF_USE_TCGEN)
+		{
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0,        "u_TCGen0",        GLSL_INT);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0VECTOR0, "u_TCGen0Vector0", GLSL_VEC3);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0VECTOR1, "u_TCGen0Vector1", GLSL_VEC3);
+		}
+
+		if (i & GENERICDEF_USE_FOG)
+		{
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGCOLORMASK, "u_FogColorMask", GLSL_VEC4);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGDISTANCE,  "u_FogDistance",  GLSL_VEC4);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGDEPTH,     "u_FogDepth",     GLSL_VEC4);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGEYET,      "u_FogEyeT",      GLSL_FLOAT);
+		}
+
+		if (i & GENERICDEF_USE_DEFORM_VERTEXES)
+		{
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DEFORMGEN,    "u_DeformGen",    GLSL_INT);
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DEFORMPARAMS, "u_DeformParams", GLSL_FLOAT5);
+		}
+
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TIME,             "u_Time",             GLSL_FLOAT);
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VIEWORIGIN,       "u_ViewOrigin",       GLSL_VEC3);
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSETEXMATRIX, "u_DiffuseTexMatrix", GLSL_MAT16);
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TEXTURE1ENV,      "u_Texture1Env",      GLSL_INT);
+
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSEMAP,       "u_DiffuseMap",       GLSL_INT);
+		GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTMAP,         "u_LightMap",         GLSL_INT);
+
+
+		if (i & GENERICDEF_USE_VERTEX_ANIMATION)
+		{
+			GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VERTEXLERP, "u_VertexLerp", GLSL_FLOAT);
+		}
+
+		GLSL_EndUniforms(&tr.genericShader[i]);
+
+		qglUseProgramObjectARB(tr.genericShader[i].program);
+		GLSL_SetUniformInt(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP);
+		GLSL_SetUniformInt(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTMAP,   TB_LIGHTMAP);
+		qglUseProgramObjectARB(0);
+
+		GLSL_FinishGPUShader(&tr.genericShader[i]);
+
+		numGenShaders++;
+	}
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+
+	if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, qtrue, NULL, qfalse, fallbackTextureColorShader_vp, fallbackTextureColorShader_fp, TEXTURECOLOR_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load texturecolor shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+	GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP,                "u_DiffuseMap",                GLSL_INT);
+
+	GLSL_EndUniforms(&tr.textureColorShader);
+
+	qglUseProgramObjectARB(tr.textureColorShader.program);
+	GLSL_SetUniformInt(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.textureColorShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD;
+
+	if (!GLSL_InitGPUShader(&tr.fogShader, "fogpass", attribs, qtrue, NULL, qtrue, fallbackFogPassShader_vp, fallbackFogPassShader_fp, FOGPASS_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load fogpass shader!\n");
+	}
+
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGDISTANCE,               "u_FogDistance",               GLSL_VEC4);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGDEPTH,                  "u_FogDepth",                  GLSL_VEC4);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGEYET,                   "u_FogEyeT",                   GLSL_FLOAT);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_DEFORMGEN,                 "u_DeformGen",                 GLSL_INT);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_DEFORMPARAMS,              "u_DeformParams",              GLSL_FLOAT5);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_TIME,                      "u_Time",                      GLSL_FLOAT);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_VERTEXLERP,                "u_VertexLerp",                GLSL_FLOAT);
+	
+	GLSL_EndUniforms(&tr.fogShader);
+	GLSL_FinishGPUShader(&tr.fogShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_NORMAL | ATTR_TEXCOORD;
+
+	if (!GLSL_InitGPUShader(&tr.dlightallShader, "dlight", attribs, qtrue, NULL, qtrue, fallbackDlightShader_vp, fallbackDlightShader_fp, DLIGHT_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load dlight shader!\n");
+	}
+
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DLIGHTINFO,                "u_DlightInfo",                GLSL_VEC4);
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DEFORMGEN,                 "u_DeformGen",                 GLSL_INT);
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DEFORMPARAMS,              "u_DeformParams",              GLSL_FLOAT5);
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_TIME,                      "u_Time",                      GLSL_FLOAT);
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+	GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	
+	GLSL_EndUniforms(&tr.dlightallShader);
+	
+	qglUseProgramObjectARB(tr.dlightallShader.program);
+	GLSL_SetUniformInt(&tr.dlightallShader, DLIGHT_UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.dlightallShader);
+
+	numEtcShaders++;
+
+
+	for (i = 0; i < LIGHTDEF_COUNT; i++)
+	{
+		// skip impossible combos
+		if ((i & LIGHTDEF_USE_NORMALMAP) && !r_normalMapping->integer)
+			continue;
+
+		if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer)
+			continue;
+
+		if ((i & LIGHTDEF_USE_SPECULARMAP) && !r_specularMapping->integer)
+			continue;
+
+		if ((i & LIGHTDEF_USE_DELUXEMAP) && !r_deluxeMapping->integer)
+			continue;
+
+		if (!((i & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHTMAP) && (i & LIGHTDEF_USE_DELUXEMAP))
+			continue;
+
+		if (!(i & LIGHTDEF_USE_NORMALMAP) && (i & LIGHTDEF_USE_PARALLAXMAP))
+			continue;
+
+		if (!((i & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHT_VECTOR))
+		{
+			if (i & LIGHTDEF_USE_SHADOWMAP)
+				continue;
+		}
+
+		attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL;
+
+		extradefines[0] = '\0';
+
+		if (r_normalAmbient->value > 0.003f)
+			Q_strcat(extradefines, 1024, va("#define r_normalAmbient %f\n", r_normalAmbient->value));
+
+		if (r_dlightMode->integer >= 2)
+			Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n");
+
+		if (1)
+		{
+			Q_strcat(extradefines, 1024, "#define SWIZZLE_NORMALMAP\n");
+		}
+
+		if (r_hdr->integer && !(glRefConfig.textureFloat && glRefConfig.halfFloatPixel))
+			Q_strcat(extradefines, 1024, "#define RGBE_LIGHTMAP\n");
+
+		if (i & LIGHTDEF_LIGHTTYPE_MASK)
+		{
+			Q_strcat(extradefines, 1024, "#define USE_LIGHT\n");
+
+			if (r_normalMapping->integer == 0 && r_specularMapping->integer == 0)
+				Q_strcat(extradefines, 1024, "#define USE_FAST_LIGHT\n");
+
+			switch (i & LIGHTDEF_LIGHTTYPE_MASK)
+			{
+				case LIGHTDEF_USE_LIGHTMAP:
+					Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n");
+					attribs |= ATTR_LIGHTCOORD | ATTR_LIGHTDIRECTION;
+					break;
+				case LIGHTDEF_USE_LIGHT_VECTOR:
+					Q_strcat(extradefines, 1024, "#define USE_LIGHT_VECTOR\n");
+					break;
+				case LIGHTDEF_USE_LIGHT_VERTEX:
+					Q_strcat(extradefines, 1024, "#define USE_LIGHT_VERTEX\n");
+					attribs |= ATTR_LIGHTDIRECTION;
+					break;
+				default:
+					break;
+			}
+		}
+
+		if ((i & LIGHTDEF_USE_NORMALMAP) && r_normalMapping->integer)
+		{
+			Q_strcat(extradefines, 1024, "#define USE_NORMALMAP\n");
+
+			if (r_normalMapping->integer == 2)
+				Q_strcat(extradefines, 1024, "#define USE_OREN_NAYAR\n");
+
+			if (r_normalMapping->integer == 3)
+				Q_strcat(extradefines, 1024, "#define USE_TRIACE_OREN_NAYAR\n");
+
+#ifdef USE_VERT_TANGENT_SPACE
+			Q_strcat(extradefines, 1024, "#define USE_VERT_TANGENT_SPACE\n");
+			attribs |= ATTR_TANGENT | ATTR_BITANGENT;
+#endif
+		}
+
+		if ((i & LIGHTDEF_USE_SPECULARMAP) && r_specularMapping->integer)
+		{
+			Q_strcat(extradefines, 1024, "#define USE_SPECULARMAP\n");
+
+			switch (r_specularMapping->integer)
+			{
+				case 1:
+				default:
+					Q_strcat(extradefines, 1024, "#define USE_TRIACE\n");
+					break;
+
+				case 2:
+					Q_strcat(extradefines, 1024, "#define USE_BLINN\n");
+					break;
+
+				case 3:
+					Q_strcat(extradefines, 1024, "#define USE_COOK_TORRANCE\n");
+					break;
+
+				case 4:
+					Q_strcat(extradefines, 1024, "#define USE_TORRANCE_SPARROW\n");
+					break;
+			}
+		}
+
+		if ((i & LIGHTDEF_USE_DELUXEMAP) && r_deluxeMapping->integer)
+			Q_strcat(extradefines, 1024, "#define USE_DELUXEMAP\n");
+
+		if ((i & LIGHTDEF_USE_PARALLAXMAP) && !(i & LIGHTDEF_ENTITY) && r_parallaxMapping->integer)
+			Q_strcat(extradefines, 1024, "#define USE_PARALLAXMAP\n");
+
+		if (i & LIGHTDEF_USE_SHADOWMAP)
+			Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n");
+
+		if (i & LIGHTDEF_TCGEN_ENVIRONMENT)
+			Q_strcat(extradefines, 1024, "#define TCGEN_ENVIRONMENT\n");
+
+		if (i & LIGHTDEF_ENTITY)
+		{
+			Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n");
+			attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
+
+#ifdef USE_VERT_TANGENT_SPACE
+			if (i & LIGHTDEF_USE_NORMALMAP && r_normalMapping->integer)
+			{
+				attribs |= ATTR_TANGENT2 | ATTR_BITANGENT2;
+			}
+#endif
+		}
+
+		if (!GLSL_InitGPUShader(&tr.lightallShader[i], "lightall", attribs, qtrue, extradefines, qtrue, fallbackLightallShader_vp, fallbackLightallShader_fp, GENERIC_UNIFORM_COUNT))
+		{
+			ri.Error(ERR_FATAL, "Could not load lightall shader!\n");
+		}
+
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MODELMATRIX,               "u_ModelMatrix",               GLSL_MAT16);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSETEXMATRIX,          "u_DiffuseTexMatrix",          GLSL_MAT16);
+		//GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALTEXMATRIX,           "u_NormalTexMatrix",           GLSL_MAT16);
+		//GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARTEXMATRIX,         "u_SpecularTexMatrix",         GLSL_MAT16);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VIEWORIGIN,                "u_ViewOrigin",                GLSL_VEC3);
+
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSEMAP,                "u_DiffuseMap",                GLSL_INT);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTMAP,                  "u_LightMap",                  GLSL_INT);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALMAP,                 "u_NormalMap",                 GLSL_INT);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DELUXEMAP,                 "u_DeluxeMap",                 GLSL_INT);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARMAP,               "u_SpecularMap",               GLSL_INT);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SHADOWMAP,                 "u_ShadowMap",                 GLSL_INT);
+
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_AMBIENTLIGHT,              "u_AmbientLight",              GLSL_VEC3);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIRECTEDLIGHT,             "u_DirectedLight",             GLSL_VEC3);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTORIGIN,               "u_LightOrigin",               GLSL_VEC4);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTRADIUS,               "u_LightRadius",               GLSL_FLOAT);
+
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MATERIALINFO,              "u_MaterialInfo",              GLSL_VEC2);
+
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_BASECOLOR,                 "u_BaseColor",                 GLSL_VEC4);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VERTCOLOR,                 "u_VertColor",                 GLSL_VEC4);
+		GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VERTEXLERP,                "u_VertexLerp",                GLSL_FLOAT);
+		
+		GLSL_EndUniforms(&tr.lightallShader[i]);
+
+		qglUseProgramObjectARB(tr.lightallShader[i].program);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSEMAP,  TB_DIFFUSEMAP);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTMAP,    TB_LIGHTMAP);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALMAP,   TB_NORMALMAP);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_DELUXEMAP,   TB_DELUXEMAP);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARMAP, TB_SPECULARMAP);
+		GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_SHADOWMAP,   TB_SHADOWMAP);
+		qglUseProgramObjectARB(0);
+
+		GLSL_FinishGPUShader(&tr.lightallShader[i]);
+
+		numLightShaders++;
+	}
+	
+	attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD;
+
+	extradefines[0] = '\0';
+
+	if (!GLSL_InitGPUShader(&tr.shadowmapShader, "shadowfill", attribs, qtrue, extradefines, qtrue, fallbackShadowfillShader_vp, fallbackShadowfillShader_fp, GENERIC_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load shadowfill shader!\n");
+	}
+
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_DEFORMGEN,                 "u_DeformGen",                 GLSL_INT);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_DEFORMPARAMS,              "u_DeformParams",              GLSL_FLOAT5);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_TIME,                      "u_Time",                      GLSL_FLOAT);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_MODELMATRIX,               "u_ModelMatrix",               GLSL_MAT16);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_VERTEXLERP,                "u_VertexLerp",                GLSL_FLOAT);
+	
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_LIGHTORIGIN,               "u_LightOrigin",               GLSL_VEC4);
+	GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_LIGHTRADIUS,               "u_LightRadius",               GLSL_FLOAT);
+
+	GLSL_EndUniforms(&tr.shadowmapShader);
+	GLSL_FinishGPUShader(&tr.shadowmapShader);
+
+	numEtcShaders++;
+
+	attribs = ATTR_POSITION | ATTR_NORMAL;
+	extradefines[0] = '\0';
+
+	Q_strcat(extradefines, 1024, "#define USE_PCF\n#define USE_DISCARD\n");
+
+	if (!GLSL_InitGPUShader(&tr.pshadowShader, "pshadow", attribs, qtrue, extradefines, qtrue, fallbackPshadowShader_vp, fallbackPshadowShader_fp, PSHADOW_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load pshadow shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTFORWARD,              "u_LightForward",              GLSL_VEC3);
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTUP,                   "u_LightUp",                   GLSL_VEC3);
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTRIGHT,                "u_LightRight",                GLSL_VEC3);
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTORIGIN,               "u_LightOrigin",               GLSL_VEC4);
+	GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTRADIUS,               "u_LightRadius",               GLSL_FLOAT);
+
+	GLSL_EndUniforms(&tr.pshadowShader);
+
+	qglUseProgramObjectARB(tr.pshadowShader.program);
+	GLSL_SetUniformInt(&tr.pshadowShader, PSHADOW_UNIFORM_SHADOWMAP, TB_DIFFUSEMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.pshadowShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+	extradefines[0] = '\0';
+
+	if (!GLSL_InitGPUShader(&tr.down4xShader, "down4x", attribs, qtrue, extradefines, qtrue, fallbackDown4xShader_vp, fallbackDown4xShader_fp, TEXTURECOLOR_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load down4x shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_INVTEXRES,                 "u_InvTexRes",                 GLSL_VEC2);
+
+	GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP,                "u_TextureMap",                GLSL_INT);
+
+	GLSL_EndUniforms(&tr.down4xShader);
+
+	qglUseProgramObjectARB(tr.down4xShader.program);
+	GLSL_SetUniformInt(&tr.down4xShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.down4xShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+	extradefines[0] = '\0';
+
+	if (!GLSL_InitGPUShader(&tr.bokehShader, "bokeh", attribs, qtrue, extradefines, qtrue, fallbackBokehShader_vp, fallbackBokehShader_fp, TEXTURECOLOR_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load bokeh shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_INVTEXRES,                 "u_InvTexRes",                 GLSL_VEC2);
+	GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+
+	GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP,                "u_TextureMap",                GLSL_INT);
+
+	GLSL_EndUniforms(&tr.bokehShader);
+
+	qglUseProgramObjectARB(tr.bokehShader.program);
+	GLSL_SetUniformInt(&tr.bokehShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.bokehShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+	extradefines[0] = '\0';
+
+	if (!GLSL_InitGPUShader(&tr.tonemapShader, "tonemap", attribs, qtrue, extradefines, qtrue, fallbackToneMapShader_vp, fallbackToneMapShader_fp, TEXTURECOLOR_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load tonemap shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_INVTEXRES,                 "u_InvTexRes",                 GLSL_VEC2);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX,        "u_AutoExposureMinMax",        GLSL_VEC2);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR,       "u_ToneMinAvgMaxLinear",       GLSL_VEC3);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP,                "u_TextureMap",                GLSL_INT);
+	GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_LEVELSMAP,                 "u_LevelsMap",                 GLSL_INT);
+
+	GLSL_EndUniforms(&tr.tonemapShader);
+
+	qglUseProgramObjectARB(tr.tonemapShader.program);
+	GLSL_SetUniformInt(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_COLORMAP);
+	GLSL_SetUniformInt(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_LEVELSMAP,  TB_LEVELSMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.tonemapShader);
+
+	numEtcShaders++;
+
+
+	for (i = 0; i < 2; i++)
+	{
+		attribs = ATTR_POSITION | ATTR_TEXCOORD;
+		extradefines[0] = '\0';
+
+		if (!i)
+			Q_strcat(extradefines, 1024, "#define FIRST_PASS\n");
+
+		if (!GLSL_InitGPUShader(&tr.calclevels4xShader[i], "calclevels4x", attribs, qtrue, extradefines, qtrue, fallbackCalcLevels4xShader_vp, fallbackCalcLevels4xShader_fp, TEXTURECOLOR_UNIFORM_COUNT))
+		{
+			ri.Error(ERR_FATAL, "Could not load calclevels4x shader!\n");
+		}
+
+		GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16);
+		GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_INVTEXRES,                 "u_InvTexRes",                 GLSL_VEC2);
+		GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_COLOR,                     "u_Color",                     GLSL_VEC4);
+
+		GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_TEXTUREMAP,                "u_TextureMap",                GLSL_INT);
+
+		GLSL_EndUniforms(&tr.calclevels4xShader[i]);
+
+		qglUseProgramObjectARB(tr.calclevels4xShader[i].program);
+		GLSL_SetUniformInt(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+		qglUseProgramObjectARB(0);
+
+		GLSL_FinishGPUShader(&tr.calclevels4xShader[i]);
+
+		numEtcShaders++;		
+	}
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+	extradefines[0] = '\0';
+
+	if (r_shadowFilter->integer >= 1)
+		Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
+
+	if (r_shadowFilter->integer >= 2)
+		Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
+
+	Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
+
+	Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %d\n", r_shadowMapSize->integer));
+	Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
+
+
+	if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShadowmaskShader_vp, fallbackShadowmaskShader_fp, SHADOWMASK_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load shadowmask shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP,  "u_ShadowMvp",   GLSL_MAT16);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP2, "u_ShadowMvp2",  GLSL_MAT16);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP3, "u_ShadowMvp3",  GLSL_MAT16);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWORIGIN, "u_ViewOrigin",  GLSL_VEC3);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWINFO,   "u_ViewInfo",    GLSL_VEC4);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWFORWARD,"u_ViewForward", GLSL_VEC3);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWLEFT,   "u_ViewLeft",    GLSL_VEC3);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWUP,     "u_ViewUp",      GLSL_VEC3);
+
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP,      "u_ShadowMap",      GLSL_INT);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP2,     "u_ShadowMap2",     GLSL_INT);
+	GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP3,     "u_ShadowMap3",     GLSL_INT);
+
+	GLSL_EndUniforms(&tr.shadowmaskShader);
+
+	qglUseProgramObjectARB(tr.shadowmaskShader.program);
+	GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+	GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP,  TB_SHADOWMAP);
+	GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
+	GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.shadowmaskShader);
+
+	numEtcShaders++;
+
+
+	attribs = ATTR_POSITION | ATTR_TEXCOORD;
+	extradefines[0] = '\0';
+
+	if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackSsaoShader_vp, fallbackSsaoShader_fp, SSAO_UNIFORM_COUNT))
+	{
+		ri.Error(ERR_FATAL, "Could not load ssao shader!\n");
+	}
+	
+	GLSL_AddUniform(&tr.ssaoShader, SSAO_UNIFORM_VIEWINFO,   "u_ViewInfo",    GLSL_VEC4);
+
+	GLSL_AddUniform(&tr.ssaoShader, SSAO_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT);
+
+	GLSL_EndUniforms(&tr.ssaoShader);
+
+	qglUseProgramObjectARB(tr.ssaoShader.program);
+	GLSL_SetUniformInt(&tr.ssaoShader, SSAO_UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+	qglUseProgramObjectARB(0);
+
+	GLSL_FinishGPUShader(&tr.ssaoShader);
+
+	numEtcShaders++;
+
+
+	for (i = 0; i < 2; i++)
+	{
+		attribs = ATTR_POSITION | ATTR_TEXCOORD;
+		extradefines[0] = '\0';
+
+		if (i & 1)
+			Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
+		else
+			Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
+
+
+		if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackDepthBlurShader_vp, fallbackDepthBlurShader_fp, DEPTHBLUR_UNIFORM_COUNT))
+		{
+			ri.Error(ERR_FATAL, "Could not load depthBlur shader!\n");
+		}
+		
+		GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_VIEWINFO,   "u_ViewInfo",    GLSL_VEC4);
+
+		GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENIMAGEMAP, "u_ScreenImageMap", GLSL_INT);
+		GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT);
+
+		GLSL_EndUniforms(&tr.depthBlurShader[i]);
+
+		qglUseProgramObjectARB(tr.depthBlurShader[i].program);
+		GLSL_SetUniformInt(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
+		GLSL_SetUniformInt(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
+		qglUseProgramObjectARB(0);
+
+		GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
+
+		numEtcShaders++;
+	}
+
+
+	endTime = ri.Milliseconds();
+
+	ri.Printf(PRINT_ALL, "loaded %i GLSL shaders (%i gen %i light %i etc) in %5.2f seconds\n", 
+		numGenShaders + numLightShaders + numEtcShaders, numGenShaders, numLightShaders, 
+		numEtcShaders, (endTime - startTime) / 1000.0);
+
+	if (0)
+	{
+		GLSL_LoadGPUShaderText("Generic", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Generic", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("TextureColor", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("TextureColor", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("FogPass", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("FogPass", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+		
+		GLSL_LoadGPUShaderText("Dlight", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Dlight", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("Lightall", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Lightall", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+		
+		GLSL_LoadGPUShaderText("Shadowfill", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Shadowfill", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+		
+		GLSL_LoadGPUShaderText("Pshadow", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Pshadow", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("Down4x", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Down4x", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("Bokeh", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Bokeh", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("ToneMap", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("ToneMap", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("CalcLevels4x", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("CalcLevels4x", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("Shadowmask", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Shadowmask", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("Ssao", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("Ssao", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+
+		GLSL_LoadGPUShaderText("DepthBlur", NULL, GL_VERTEX_SHADER_ARB,   NULL, 0, qtrue);
+		GLSL_LoadGPUShaderText("DepthBlur", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue);
+	}
+
+}
+
+void GLSL_ShutdownGPUShaders(void)
+{
+	int i;
+
+	ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
+
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
+#ifdef USE_VERT_TANGENT_SPACE
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT);
+#endif
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
+#ifdef USE_VERT_TANGENT_SPACE
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2);
+#endif
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
+	qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
+	GLSL_BindNullProgram();
+
+	for ( i = 0; i < GENERICDEF_COUNT; i++)
+		GLSL_DeleteGPUShader(&tr.genericShader[i]);
+
+	GLSL_DeleteGPUShader(&tr.textureColorShader);
+	GLSL_DeleteGPUShader(&tr.fogShader);
+	GLSL_DeleteGPUShader(&tr.dlightallShader);
+
+	for ( i = 0; i < LIGHTDEF_COUNT; i++)
+		GLSL_DeleteGPUShader(&tr.lightallShader[i]);
+
+	GLSL_DeleteGPUShader(&tr.shadowmapShader);
+	GLSL_DeleteGPUShader(&tr.pshadowShader);
+	GLSL_DeleteGPUShader(&tr.down4xShader);
+	
+	for ( i = 0; i < 2; i++)
+		GLSL_DeleteGPUShader(&tr.calclevels4xShader[i]);
+
+	glState.currentProgram = 0;
+	qglUseProgramObjectARB(0);
+}
+
+
+void GLSL_BindProgram(shaderProgram_t * program)
+{
+	if(!program)
+	{
+		GLSL_BindNullProgram();
+		return;
+	}
+
+	if(r_logFile->integer)
+	{
+		// don't just call LogComment, or we will get a call to va() every frame!
+		GLimp_LogComment(va("--- GL_BindProgram( %s ) ---\n", program->name));
+	}
+
+	if(glState.currentProgram != program)
+	{
+		qglUseProgramObjectARB(program->program);
+		glState.currentProgram = program;
+		backEnd.pc.c_glslShaderBinds++;
+	}
+}
+
+
+void GLSL_BindNullProgram(void)
+{
+	if(r_logFile->integer)
+	{
+		GLimp_LogComment("--- GL_BindNullProgram ---\n");
+	}
+
+	if(glState.currentProgram)
+	{
+		qglUseProgramObjectARB(0);
+		glState.currentProgram = NULL;
+	}
+}
+
+
+void GLSL_VertexAttribsState(uint32_t stateBits)
+{
+	uint32_t		diff;
+
+	GLSL_VertexAttribPointers(stateBits);
+
+	diff = stateBits ^ glState.vertexAttribsState;
+	if(!diff)
+	{
+		return;
+	}
+
+	if(diff & ATTR_POSITION)
+	{
+		if(stateBits & ATTR_POSITION)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION);
+		}
+	}
+
+	if(diff & ATTR_TEXCOORD)
+	{
+		if(stateBits & ATTR_TEXCOORD)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0);
+		}
+	}
+
+	if(diff & ATTR_LIGHTCOORD)
+	{
+		if(stateBits & ATTR_LIGHTCOORD)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1);
+		}
+	}
+
+	if(diff & ATTR_NORMAL)
+	{
+		if(stateBits & ATTR_NORMAL)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL);
+		}
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if(diff & ATTR_TANGENT)
+	{
+		if(stateBits & ATTR_TANGENT)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT);
+		}
+	}
+
+	if(diff & ATTR_BITANGENT)
+	{
+		if(stateBits & ATTR_BITANGENT)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_BITANGENT )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_BITANGENT);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_BITANGENT )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT);
+		}
+	}
+#endif
+
+	if(diff & ATTR_COLOR)
+	{
+		if(stateBits & ATTR_COLOR)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_COLOR);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR);
+		}
+	}
+
+	if(diff & ATTR_LIGHTDIRECTION)
+	{
+		if(stateBits & ATTR_LIGHTDIRECTION)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION);
+		}
+	}
+
+	if(diff & ATTR_POSITION2)
+	{
+		if(stateBits & ATTR_POSITION2)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2);
+		}
+	}
+
+	if(diff & ATTR_NORMAL2)
+	{
+		if(stateBits & ATTR_NORMAL2)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2);
+		}
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if(diff & ATTR_TANGENT2)
+	{
+		if(stateBits & ATTR_TANGENT2)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2);
+		}
+	}
+
+	if(diff & ATTR_BITANGENT2)
+	{
+		if(stateBits & ATTR_BITANGENT2)
+		{
+			GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_BITANGENT2 )\n");
+			qglEnableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2);
+		}
+		else
+		{
+			GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_BITANGENT2 )\n");
+			qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2);
+		}
+	}
+#endif
+
+	glState.vertexAttribsState = stateBits;
+}
+
+void GLSL_VertexAttribPointers(uint32_t attribBits)
+{
+	if(!glState.currentVBO)
+	{
+		ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VBO bound");
+		return;
+	}
+
+	// don't just call LogComment, or we will get a call to va() every frame!
+	GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", glState.currentVBO->name));
+
+	if((attribBits & ATTR_POSITION) && !(glState.vertexAttribPointersSet & ATTR_POSITION))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, glState.currentVBO->stride_xyz, BUFFER_OFFSET(glState.currentVBO->ofs_xyz + glState.vertexAttribsNewFrame * glState.currentVBO->size_xyz));
+		glState.vertexAttribPointersSet |= ATTR_POSITION;
+	}
+
+	if((attribBits & ATTR_TEXCOORD) && !(glState.vertexAttribPointersSet & ATTR_TEXCOORD))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD0, 2, GL_FLOAT, 0, glState.currentVBO->stride_st, BUFFER_OFFSET(glState.currentVBO->ofs_st));
+		glState.vertexAttribPointersSet |= ATTR_TEXCOORD;
+	}
+
+	if((attribBits & ATTR_LIGHTCOORD) && !(glState.vertexAttribPointersSet & ATTR_LIGHTCOORD))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTCOORD )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD1, 2, GL_FLOAT, 0, glState.currentVBO->stride_lightmap, BUFFER_OFFSET(glState.currentVBO->ofs_lightmap));
+		glState.vertexAttribPointersSet |= ATTR_LIGHTCOORD;
+	}
+
+	if((attribBits & ATTR_NORMAL) && !(glState.vertexAttribPointersSet & ATTR_NORMAL))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL, 3, GL_FLOAT, 0, glState.currentVBO->stride_normal, BUFFER_OFFSET(glState.currentVBO->ofs_normal + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal));
+		glState.vertexAttribPointersSet |= ATTR_NORMAL;
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if((attribBits & ATTR_TANGENT) && !(glState.vertexAttribPointersSet & ATTR_TANGENT))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT, 3, GL_FLOAT, 0, glState.currentVBO->stride_tangent, BUFFER_OFFSET(glState.currentVBO->ofs_tangent + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal)); // FIXME
+		glState.vertexAttribPointersSet |= ATTR_TANGENT;
+	}
+
+	if((attribBits & ATTR_BITANGENT) && !(glState.vertexAttribPointersSet & ATTR_BITANGENT))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_BITANGENT )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_BITANGENT, 3, GL_FLOAT, 0, glState.currentVBO->stride_bitangent, BUFFER_OFFSET(glState.currentVBO->ofs_bitangent + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal)); // FIXME
+		glState.vertexAttribPointersSet |= ATTR_BITANGENT;
+	}
+#endif
+
+	if((attribBits & ATTR_COLOR) && !(glState.vertexAttribPointersSet & ATTR_COLOR))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_COLOR )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_COLOR, 4, GL_FLOAT, 0, glState.currentVBO->stride_vertexcolor, BUFFER_OFFSET(glState.currentVBO->ofs_vertexcolor));
+		glState.vertexAttribPointersSet |= ATTR_COLOR;
+	}
+
+	if((attribBits & ATTR_LIGHTDIRECTION) && !(glState.vertexAttribPointersSet & ATTR_LIGHTDIRECTION))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTDIRECTION )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_LIGHTDIRECTION, 3, GL_FLOAT, 0, glState.currentVBO->stride_lightdir, BUFFER_OFFSET(glState.currentVBO->ofs_lightdir));
+		glState.vertexAttribPointersSet |= ATTR_LIGHTDIRECTION;
+	}
+
+	if((attribBits & ATTR_POSITION2) && !(glState.vertexAttribPointersSet & ATTR_POSITION2))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION2 )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_POSITION2, 3, GL_FLOAT, 0, glState.currentVBO->stride_xyz, BUFFER_OFFSET(glState.currentVBO->ofs_xyz + glState.vertexAttribsOldFrame * glState.currentVBO->size_xyz));
+		glState.vertexAttribPointersSet |= ATTR_POSITION2;
+	}
+
+	if((attribBits & ATTR_NORMAL2) && !(glState.vertexAttribPointersSet & ATTR_NORMAL2))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL2 )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_NORMAL2, 3, GL_FLOAT, 0, glState.currentVBO->stride_normal, BUFFER_OFFSET(glState.currentVBO->ofs_normal + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal));
+		glState.vertexAttribPointersSet |= ATTR_NORMAL2;
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if((attribBits & ATTR_TANGENT2) && !(glState.vertexAttribPointersSet & ATTR_TANGENT2))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT2 )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_TANGENT2, 3, GL_FLOAT, 0, glState.currentVBO->stride_tangent, BUFFER_OFFSET(glState.currentVBO->ofs_tangent + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal)); // FIXME
+		glState.vertexAttribPointersSet |= ATTR_TANGENT2;
+	}
+
+	if((attribBits & ATTR_BITANGENT2) && !(glState.vertexAttribPointersSet & ATTR_BITANGENT2))
+	{
+		GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_BITANGENT2 )\n");
+
+		qglVertexAttribPointerARB(ATTR_INDEX_BITANGENT2, 3, GL_FLOAT, 0, glState.currentVBO->stride_bitangent, BUFFER_OFFSET(glState.currentVBO->ofs_bitangent + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal)); // FIXME
+		glState.vertexAttribPointersSet |= ATTR_BITANGENT2;
+	}
+#endif
+
+}
+
+shaderProgram_t *GLSL_GetGenericShaderProgram(int stage)
+{
+	shaderStage_t *pStage = tess.xstages[stage];
+	int shaderAttribs = 0;
+
+	if (tess.fogNum && pStage->adjustColorsForFog)
+	{
+		shaderAttribs |= GENERICDEF_USE_FOG;
+	}
+
+	if (pStage->bundle[1].image[0] && tess.shader->multitextureEnv)
+	{
+		shaderAttribs |= GENERICDEF_USE_LIGHTMAP;
+	}
+
+	switch (pStage->rgbGen)
+	{
+		case CGEN_LIGHTING_DIFFUSE:
+			shaderAttribs |= GENERICDEF_USE_RGBAGEN;
+			break;
+		default:
+			break;
+	}
+
+	switch (pStage->alphaGen)
+	{
+		case AGEN_LIGHTING_SPECULAR:
+		case AGEN_PORTAL:
+		case AGEN_FRESNEL:
+			shaderAttribs |= GENERICDEF_USE_RGBAGEN;
+			break;
+		default:
+			break;
+	}
+
+	if (pStage->bundle[0].tcGen != TCGEN_TEXTURE)
+	{
+		shaderAttribs |= GENERICDEF_USE_TCGEN;
+	}
+
+	if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader))
+	{
+		shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES;
+	}
+
+	if (glState.vertexAttribsInterpolation > 0.0f && backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+	{
+		shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION;
+	}
+
+	return &tr.genericShader[shaderAttribs];
+}

Added: trunk/code/rend2/tr_image.c
===================================================================
--- trunk/code/rend2/tr_image.c	                        (rev 0)
+++ trunk/code/rend2/tr_image.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,3431 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_image.c
+#include "tr_local.h"
+
+static byte			 s_intensitytable[256];
+static unsigned char s_gammatable[256];
+
+int		gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
+int		gl_filter_max = GL_LINEAR;
+
+#define FILE_HASH_SIZE		1024
+static	image_t*		hashTable[FILE_HASH_SIZE];
+
+/*
+** R_GammaCorrect
+*/
+void R_GammaCorrect( byte *buffer, int bufSize ) {
+	int i;
+
+	for ( i = 0; i < bufSize; i++ ) {
+		buffer[i] = s_gammatable[buffer[i]];
+	}
+}
+
+typedef struct {
+	char *name;
+	int	minimize, maximize;
+} textureMode_t;
+
+textureMode_t modes[] = {
+	{"GL_NEAREST", GL_NEAREST, GL_NEAREST},
+	{"GL_LINEAR", GL_LINEAR, GL_LINEAR},
+	{"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
+	{"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
+	{"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
+	{"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
+};
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long generateHashValue( const char *fname ) {
+	int		i;
+	long	hash;
+	char	letter;
+
+	hash = 0;
+	i = 0;
+	while (fname[i] != '\0') {
+		letter = tolower(fname[i]);
+		if (letter =='.') break;				// don't include extension
+		if (letter =='\\') letter = '/';		// damn path names
+		hash+=(long)(letter)*(i+119);
+		i++;
+	}
+	hash &= (FILE_HASH_SIZE-1);
+	return hash;
+}
+
+/*
+===============
+GL_TextureMode
+===============
+*/
+void GL_TextureMode( const char *string ) {
+	int		i;
+	image_t	*glt;
+
+	for ( i=0 ; i< 6 ; i++ ) {
+		if ( !Q_stricmp( modes[i].name, string ) ) {
+			break;
+		}
+	}
+
+	// hack to prevent trilinear from being set on voodoo,
+	// because their driver freaks...
+	if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) {
+		ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" );
+		i = 3;
+	}
+
+
+	if ( i == 6 ) {
+		ri.Printf (PRINT_ALL, "bad filter name\n");
+		return;
+	}
+
+	gl_filter_min = modes[i].minimize;
+	gl_filter_max = modes[i].maximize;
+
+	// change all the existing mipmap texture objects
+	for ( i = 0 ; i < tr.numImages ; i++ ) {
+		glt = tr.images[ i ];
+		if ( glt->flags & IMGFLAG_MIPMAP ) {
+			GL_Bind (glt);
+			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
+		}
+	}
+}
+
+/*
+===============
+R_SumOfUsedImages
+===============
+*/
+int R_SumOfUsedImages( void ) {
+	int	total;
+	int i;
+
+	total = 0;
+	for ( i = 0; i < tr.numImages; i++ ) {
+		if ( tr.images[i]->frameUsed == tr.frameCount ) {
+			total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight;
+		}
+	}
+
+	return total;
+}
+
+/*
+===============
+R_ImageList_f
+===============
+*/
+void R_ImageList_f( void ) {
+#if 1
+	int i;
+	int estTotalSize = 0;
+
+	ri.Printf(PRINT_ALL, "\n      -w-- -h-- type  -size- --name-------\n");
+
+	for ( i = 0 ; i < tr.numImages ; i++ )
+	{
+		image_t *image = tr.images[i];
+		char *format = "???? ";
+		char *sizeSuffix;
+		int estSize;
+		int displaySize;
+
+		estSize = image->uploadHeight * image->uploadWidth;
+
+		switch(image->internalFormat)
+		{
+			case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+				format = "sDXT1";
+				// 64 bits per 16 pixels, so 4 bits per pixel
+				estSize /= 2;
+				break;
+			case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+				format = "sDXT5";
+				// 128 bits per 16 pixels, so 1 byte per pixel
+				break;
+			case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
+				format = "sBPTC";
+				// 128 bits per 16 pixels, so 1 byte per pixel
+				break;
+			case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
+				format = "LATC ";
+				// 128 bits per 16 pixels, so 1 byte per pixel
+				break;
+			case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+				format = "DXT1 ";
+				// 64 bits per 16 pixels, so 4 bits per pixel
+				estSize /= 2;
+				break;
+			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+				format = "DXT5 ";
+				// 128 bits per 16 pixels, so 1 byte per pixel
+				break;
+			case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
+				format = "BPTC ";
+				// 128 bits per 16 pixels, so 1 byte per pixel
+				break;
+			case GL_RGB4_S3TC:
+				format = "S3TC ";
+				// same as DXT1?
+				estSize /= 2;
+				break;
+			case GL_RGBA4:
+			case GL_RGBA8:
+			case GL_RGBA:
+				format = "RGBA ";
+				// 4 bytes per pixel
+				estSize *= 4;
+				break;
+			case GL_LUMINANCE8:
+			case GL_LUMINANCE16:
+			case GL_LUMINANCE:
+				format = "L    ";
+				// 1 byte per pixel?
+				break;
+			case GL_RGB5:
+			case GL_RGB8:
+			case GL_RGB:
+				format = "RGB  ";
+				// 3 bytes per pixel?
+				estSize *= 3;
+				break;
+			case GL_LUMINANCE8_ALPHA8:
+			case GL_LUMINANCE16_ALPHA16:
+			case GL_LUMINANCE_ALPHA:
+				format = "LA   ";
+				// 2 bytes per pixel?
+				estSize *= 2;
+				break;
+			case GL_SRGB_EXT:
+			case GL_SRGB8_EXT:
+				format = "sRGB ";
+				// 3 bytes per pixel?
+				estSize *= 3;
+				break;
+			case GL_SRGB_ALPHA_EXT:
+			case GL_SRGB8_ALPHA8_EXT:
+				format = "sRGBA";
+				// 4 bytes per pixel?
+				estSize *= 4;
+				break;
+			case GL_SLUMINANCE_EXT:
+			case GL_SLUMINANCE8_EXT:
+				format = "sL   ";
+				// 1 byte per pixel?
+				break;
+			case GL_SLUMINANCE_ALPHA_EXT:
+			case GL_SLUMINANCE8_ALPHA8_EXT:
+				format = "sLA  ";
+				// 2 byte per pixel?
+				estSize *= 2;
+				break;
+		}
+
+		// mipmap adds about 50%
+		if (image->flags & IMGFLAG_MIPMAP)
+			estSize += estSize / 2;
+
+		sizeSuffix = "b ";
+		displaySize = estSize;
+
+		if (displaySize > 1024)
+		{
+			displaySize /= 1024;
+			sizeSuffix = "kb";
+		}
+
+		if (displaySize > 1024)
+		{
+			displaySize /= 1024;
+			sizeSuffix = "Mb";
+		}
+
+		if (displaySize > 1024)
+		{
+			displaySize /= 1024;
+			sizeSuffix = "Gb";
+		}
+
+		ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName);
+		estTotalSize += estSize;
+	}
+
+	ri.Printf (PRINT_ALL, " ---------\n");
+	ri.Printf (PRINT_ALL, " approx %i bytes\n", estTotalSize);
+	ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages );
+#else
+	int		i;
+	image_t	*image;
+	int		texels;
+	const char *yesno[] = {
+		"no ", "yes"
+	};
+
+	ri.Printf (PRINT_ALL, "\n      -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n");
+	texels = 0;
+
+	for ( i = 0 ; i < tr.numImages ; i++ ) {
+		image = tr.images[ i ];
+
+		texels += image->uploadWidth*image->uploadHeight;
+		ri.Printf (PRINT_ALL,  "%4i: %4i %4i  %s   %d   ",
+			i, image->uploadWidth, image->uploadHeight, yesno[(image->flags & IMGFLAG_MIPMAP) ? 1 : 0], image->TMU );
+		switch ( image->internalFormat ) {
+		case 1:
+			ri.Printf( PRINT_ALL, "I    " );
+			break;
+		case 2:
+			ri.Printf( PRINT_ALL, "IA   " );
+			break;
+		case 3:
+			ri.Printf( PRINT_ALL, "RGB  " );
+			break;
+		case 4:
+			ri.Printf( PRINT_ALL, "RGBA " );
+			break;
+		case GL_RGBA8:
+			ri.Printf( PRINT_ALL, "RGBA8" );
+			break;
+		case GL_RGB8:
+			ri.Printf( PRINT_ALL, "RGB8" );
+			break;
+		case GL_RGB4_S3TC:
+			ri.Printf( PRINT_ALL, "S3TC " );
+			break;
+		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+			ri.Printf( PRINT_ALL, "DXT1 " );
+			break;
+		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+			ri.Printf( PRINT_ALL, "DXT5 " );
+			break;
+		case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
+			ri.Printf( PRINT_ALL, "LATC " );
+			break;
+		case GL_RGBA4:
+			ri.Printf( PRINT_ALL, "RGBA4" );
+			break;
+		case GL_RGB5:
+			ri.Printf( PRINT_ALL, "RGB5 " );
+			break;
+		case GL_SRGB_EXT:
+			ri.Printf( PRINT_ALL, "sRGB " );
+			break;
+		case GL_SRGB8_EXT:
+			ri.Printf( PRINT_ALL, "sRGB8" );
+			break;
+		case GL_SRGB_ALPHA_EXT:
+		case GL_SRGB8_ALPHA8_EXT:
+			ri.Printf( PRINT_ALL, "sRGBA" );
+			break;
+			/*
+		case GL_SLUMINANCE_EXT:
+			break;
+		case GL_SLUMINANCE8_EXT:
+			break;
+		case GL_SLUMINANCE_ALPHA_EXT:
+			break;
+		case GL_SLUMINANCE8_ALPHA8_EXT:
+			break;
+			*/
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+			ri.Printf( PRINT_ALL, "sDXT1" );
+			break;
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+			ri.Printf( PRINT_ALL, "sDXT5" );
+			break;
+		case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
+			ri.Printf( PRINT_ALL, "BPTC " );
+			break;
+		case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
+			ri.Printf( PRINT_ALL, "sBPTC" );
+			break;
+		default:
+			ri.Printf( PRINT_ALL, "???? " );
+		}
+
+		if (image->flags & IMGFLAG_CLAMPTOEDGE)
+			ri.Printf( PRINT_ALL, "clmp " );
+		else
+			ri.Printf( PRINT_ALL, "rept " );
+		
+		ri.Printf( PRINT_ALL, " %s\n", image->imgName );
+	}
+	ri.Printf (PRINT_ALL, " ---------\n");
+	ri.Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels);
+	ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages );
+#endif
+}
+
+//=======================================================================
+
+/*
+================
+ResampleTexture
+
+Used to resample images in a more general than quartering fashion.
+
+This will only be filtered properly if the resampled size
+is greater than half the original size.
+
+If a larger shrinking is needed, use the mipmap function 
+before or after.
+================
+*/
+static void ResampleTexture( byte *in, int inwidth, int inheight, byte *out,  
+							int outwidth, int outheight ) {
+	int		i, j;
+	byte	*inrow, *inrow2;
+	int		frac, fracstep;
+	int		p1[2048], p2[2048];
+	byte	*pix1, *pix2, *pix3, *pix4;
+
+	if (outwidth>2048)
+		ri.Error(ERR_DROP, "ResampleTexture: max width");
+								
+	fracstep = inwidth*0x10000/outwidth;
+
+	frac = fracstep>>2;
+	for ( i=0 ; i<outwidth ; i++ ) {
+		p1[i] = 4*(frac>>16);
+		frac += fracstep;
+	}
+	frac = 3*(fracstep>>2);
+	for ( i=0 ; i<outwidth ; i++ ) {
+		p2[i] = 4*(frac>>16);
+		frac += fracstep;
+	}
+
+	for (i=0 ; i<outheight ; i++) {
+		inrow = in + 4*inwidth*(int)((i+0.25)*inheight/outheight);
+		inrow2 = in + 4*inwidth*(int)((i+0.75)*inheight/outheight);
+		frac = fracstep >> 1;
+		for (j=0 ; j<outwidth ; j++) {
+			pix1 = inrow + p1[j];
+			pix2 = inrow + p2[j];
+			pix3 = inrow2 + p1[j];
+			pix4 = inrow2 + p2[j];
+			*out++ = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
+			*out++ = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
+			*out++ = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
+			*out++ = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
+		}
+	}
+}
+
+static void RGBAtoYCoCgA(const byte *in, byte *out, int width, int height)
+{
+	int x, y;
+
+	for (y = 0; y < height; y++)
+	{
+		const byte *inbyte  = in  + y * width * 4;
+		byte       *outbyte = out + y * width * 4;
+
+		for (x = 0; x < width; x++)
+		{
+			byte r, g, b, a, rb2;
+
+			r = *inbyte++;
+			g = *inbyte++;
+			b = *inbyte++;
+			a = *inbyte++;
+			rb2 = (r + b) >> 1;
+
+			*outbyte++ = (g + rb2) >> 1;       // Y  =  R/4 + G/2 + B/4
+			*outbyte++ = (r - b + 256) >> 1;   // Co =  R/2       - B/2
+			*outbyte++ = (g - rb2 + 256) >> 1; // Cg = -R/4 + G/2 - B/4
+			*outbyte++ = a;
+		}
+	}
+}
+
+static void YCoCgAtoRGBA(const byte *in, byte *out, int width, int height)
+{
+	int x, y;
+
+	for (y = 0; y < height; y++)
+	{
+		const byte *inbyte  = in  + y * width * 4;
+		byte       *outbyte = out + y * width * 4;
+
+		for (x = 0; x < width; x++)
+		{
+			byte _Y, Co, Cg, a;
+
+			_Y = *inbyte++;
+			Co = *inbyte++;
+			Cg = *inbyte++;
+			a  = *inbyte++;
+
+			*outbyte++ = CLAMP(_Y + Co - Cg,       0, 255); // R = Y + Co - Cg
+			*outbyte++ = CLAMP(_Y      + Cg - 128, 0, 255); // G = Y + Cg
+			*outbyte++ = CLAMP(_Y - Co - Cg + 256, 0, 255); // B = Y - Co - Cg
+			*outbyte++ = a;
+		}
+	}
+}
+
+
+// uses a sobel filter to change a texture to a normal map
+static void RGBAtoNormal(const byte *in, byte *out, int width, int height, qboolean clampToEdge)
+{
+	int x, y, max;
+
+	// convert to heightmap, storing in alpha
+	// same as converting to Y in YCoCg
+	max = 1;
+	for (y = 0; y < height; y++)
+	{
+		const byte *inbyte  = in  + y * width * 4;
+		byte       *outbyte = out + y * width * 4 + 3;
+
+		for (x = 0; x < width; x++)
+		{
+			*outbyte = (inbyte[0] >> 2) + (inbyte[1] >> 1) + (inbyte[2] >> 2);
+			max = MAX(max, *outbyte);
+			outbyte += 4;
+			inbyte  += 4;
+		}
+	}
+
+	// level out heights
+	if (max < 255)
+	{
+		for (y = 0; y < height; y++)
+		{
+			byte *outbyte = out + y * width * 4 + 3;
+
+			for (x = 0; x < width; x++)
+			{
+				*outbyte = *outbyte + (255 - max);
+				outbyte += 4;
+			}
+		}
+	}
+
+
+	// now run sobel filter over height values to generate X and Y
+	// then normalize
+	for (y = 0; y < height; y++)
+	{
+		byte *outbyte = out + y * width * 4;
+
+		for (x = 0; x < width; x++)
+		{
+			// 0 1 2
+			// 3 4 5
+			// 6 7 8
+
+			byte s[9];
+			int x2, y2, i;
+			vec3_t normal;
+
+			i = 0;
+			for (y2 = -1; y2 <= 1; y2++)
+			{
+				int src_y = y + y2;
+
+				if (clampToEdge)
+				{
+					src_y = CLAMP(src_y, 0, height - 1);
+				}
+				else
+				{
+					src_y = (src_y + height) % height;
+				}
+
+
+				for (x2 = -1; x2 <= 1; x2++)
+				{
+					int src_x = x + x2;
+
+					if (clampToEdge)
+					{
+						src_x = CLAMP(src_x, 0, height - 1);
+					}
+					else
+					{
+						src_x = (src_x + height) % height;
+					}
+
+					s[i++] = *(out + (src_y * width + src_x) * 4 + 3);
+				}
+			}
+
+			normal[0] =        s[0]            -     s[2]
+						 + 2 * s[3]            - 2 * s[5]
+						 +     s[6]            -     s[8];
+
+			normal[1] =        s[0] + 2 * s[1] +     s[2]
+
+						 -     s[6] - 2 * s[7] -     s[8];
+
+			normal[2] = s[4] * 4;
+
+			if (!VectorNormalize2(normal, normal))
+			{
+				VectorSet(normal, 0, 0, 1);
+			}
+
+			*outbyte++ = FloatToOffsetByte(normal[0]);
+			*outbyte++ = FloatToOffsetByte(normal[1]);
+			*outbyte++ = FloatToOffsetByte(normal[2]);
+			outbyte++;
+		}
+	}
+}
+
+#define COPYSAMPLE(a,b) *(unsigned int *)(a) = *(unsigned int *)(b)
+
+// based on Fast Curve Based Interpolation
+// from Fast Artifacts-Free Image Interpolation (http://www.andreagiachetti.it/icbi/)
+// assumes data has a 2 pixel thick border of clamped or wrapped data
+// expects data to be a grid with even (0, 0), (2, 0), (0, 2), (2, 2) etc pixels filled
+// only performs FCBI on specified component
+static void DoFCBI(byte *in, byte *out, int width, int height, int component)
+{
+	int x, y;
+	byte *outbyte, *inbyte;
+
+	// copy in to out
+	for (y = 2; y < height - 2; y += 2)
+	{
+		inbyte  = in  + (y * width + 2) * 4 + component;
+		outbyte = out + (y * width + 2) * 4 + component;
+
+		for (x = 2; x < width - 2; x += 2)
+		{
+			*outbyte = *inbyte;
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+	
+	for (y = 3; y < height - 3; y += 2)
+	{
+		// diagonals
+		//
+		// NWp  - northwest interpolated pixel
+		// NEp  - northeast interpolated pixel
+		// NWd  - northwest first derivative
+		// NEd  - northeast first derivative
+		// NWdd - northwest second derivative
+		// NEdd - northeast second derivative
+		//
+		// Uses these samples:
+		//
+		//         0
+		//   - - a - b - -
+		//   - - - - - - -
+		//   c - d - e - f
+		// 0 - - - - - - -
+		//   g - h - i - j
+		//   - - - - - - -
+		//   - - k - l - -
+		//
+		// x+2 uses these samples:
+		//
+		//         0
+		//   - - - - a - b - -
+		//   - - - - - - - - -
+		//   - - c - d - e - f
+		// 0 - - - - - - - - -
+		//   - - g - h - i - j
+		//   - - - - - - - - -
+		//   - - - - k - l - -
+		//
+		// so we can reuse 8 of them on next iteration
+		//
+		// a=b, c=d, d=e, e=f, g=h, h=i, i=j, k=l
+		//
+		// only b, f, j, and l need to be sampled on next iteration
+
+		byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl;
+		byte *line1, *line2, *line3, *line4;
+
+		x = 3;
+
+		// optimization one
+		//                       SAMPLE2(sa, x-1, y-3);
+		//SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1);
+		//SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1);
+		//                       SAMPLE2(sk, x-1, y+3);
+
+		// optimization two
+		line1 = in + ((y - 3) * width + (x - 1)) * 4 + component;
+		line2 = in + ((y - 1) * width + (x - 3)) * 4 + component;
+		line3 = in + ((y + 1) * width + (x - 3)) * 4 + component;
+		line4 = in + ((y + 3) * width + (x - 1)) * 4 + component;
+
+		//                                   COPYSAMPLE(sa, line1); line1 += 8;
+		//COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(se, line2); line2 += 8;
+		//COPYSAMPLE(sg, line3); line3 += 8; COPYSAMPLE(sh, line3); line3 += 8; COPYSAMPLE(si, line3); line3 += 8;
+		//                                   COPYSAMPLE(sk, line4); line4 += 8;
+
+		                         sa = *line1; line1 += 8;
+		sc = *line2; line2 += 8; sd = *line2; line2 += 8; se = *line2; line2 += 8;
+		sg = *line3; line3 += 8; sh = *line3; line3 += 8; si = *line3; line3 += 8;
+		                         sk = *line4; line4 += 8;
+
+		outbyte = out + (y * width + x) * 4 + component;
+
+		for ( ; x < width - 3; x += 2)
+		{
+			int NWd, NEd, NWp, NEp;
+
+			// original
+			//                       SAMPLE2(sa, x-1, y-3); SAMPLE2(sb, x+1, y-3);
+			//SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); SAMPLE2(sf, x+3, y-1);
+			//SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); SAMPLE2(sj, x+3, y+1);
+			//                       SAMPLE2(sk, x-1, y+3); SAMPLE2(sl, x+1, y+3);
+
+			// optimization one
+			//SAMPLE2(sb, x+1, y-3);
+			//SAMPLE2(sf, x+3, y-1);
+			//SAMPLE2(sj, x+3, y+1);
+			//SAMPLE2(sl, x+1, y+3);
+
+			// optimization two
+			//COPYSAMPLE(sb, line1); line1 += 8;
+			//COPYSAMPLE(sf, line2); line2 += 8;
+			//COPYSAMPLE(sj, line3); line3 += 8;
+			//COPYSAMPLE(sl, line4); line4 += 8;
+
+			sb = *line1; line1 += 8;
+			sf = *line2; line2 += 8;
+			sj = *line3; line3 += 8;
+			sl = *line4; line4 += 8;
+
+			NWp = sd + si;
+			NEp = se + sh;
+			NWd = abs(sd - si);
+			NEd = abs(se - sh);
+
+			if (NWd > 100 || NEd > 100 || abs(NWp-NEp) > 200)
+			{
+				if (NWd < NEd)
+					*outbyte = NWp >> 1;
+				else
+					*outbyte = NEp >> 1;
+			}
+			else
+			{
+				int NWdd, NEdd;
+
+				//NEdd = abs(sg + sd + sb - 3 * (se + sh) + sk + si + sf);
+				//NWdd = abs(sa + se + sj - 3 * (sd + si) + sc + sh + sl);
+				NEdd = abs(sg + sb - 3 * NEp + sk + sf + NWp);
+				NWdd = abs(sa + sj - 3 * NWp + sc + sl + NEp);
+
+				if (NWdd > NEdd)
+					*outbyte = NWp >> 1;
+				else
+					*outbyte = NEp >> 1;
+			}
+
+			outbyte += 8;
+
+			//                    COPYSAMPLE(sa, sb);
+			//COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); COPYSAMPLE(se, sf);
+			//COPYSAMPLE(sg, sh); COPYSAMPLE(sh, si); COPYSAMPLE(si, sj);
+			//                    COPYSAMPLE(sk, sl);
+
+			         sa = sb;
+			sc = sd; sd = se; se = sf;
+			sg = sh; sh = si; si = sj;
+			         sk = sl;
+		}
+	}
+
+	// hack: copy out to in again
+	for (y = 3; y < height - 3; y += 2)
+	{
+		inbyte = out + (y * width + 3) * 4 + component;
+		outbyte = in + (y * width + 3) * 4 + component;
+
+		for (x = 3; x < width - 3; x += 2)
+		{
+			*outbyte = *inbyte;
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+	
+	for (y = 2; y < height - 3; y++)
+	{
+		// horizontal & vertical
+		//
+		// hp  - horizontally interpolated pixel
+		// vp  - vertically interpolated pixel
+		// hd  - horizontal first derivative
+		// vd  - vertical first derivative
+		// hdd - horizontal second derivative
+		// vdd - vertical second derivative
+		// Uses these samples:
+		//
+		//       0
+		//   - a - b -
+		//   c - d - e
+		// 0 - f - g -
+		//   h - i - j
+		//   - k - l -
+		//
+		// x+2 uses these samples:
+		//
+		//       0
+		//   - - - a - b -
+		//   - - c - d - e
+		// 0 - - - f - g -
+		//   - - h - i - j
+		//   - - - k - l -
+		//
+		// so we can reuse 7 of them on next iteration
+		//
+		// a=b, c=d, d=e, f=g, h=i, i=j, k=l
+		//
+		// only b, e, g, j, and l need to be sampled on next iteration
+
+		byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl;
+		byte *line1, *line2, *line3, *line4, *line5;
+
+		//x = (y + 1) % 2;
+		x = (y + 1) % 2 + 2;
+		
+		// optimization one
+		//            SAMPLE2(sa, x-1, y-2);
+		//SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x,   y-1);
+		//            SAMPLE2(sf, x-1, y  );
+		//SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x,   y+1);
+		//            SAMPLE2(sk, x-1, y+2);
+
+		line1 = in + ((y - 2) * width + (x - 1)) * 4 + component;
+		line2 = in + ((y - 1) * width + (x - 2)) * 4 + component;
+		line3 = in + ((y    ) * width + (x - 1)) * 4 + component;
+		line4 = in + ((y + 1) * width + (x - 2)) * 4 + component;
+		line5 = in + ((y + 2) * width + (x - 1)) * 4 + component;
+
+		//                 COPYSAMPLE(sa, line1); line1 += 8;
+		//COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8;
+		//                 COPYSAMPLE(sf, line3); line3 += 8;
+		//COPYSAMPLE(sh, line4); line4 += 8; COPYSAMPLE(si, line4); line4 += 8;
+        //                 COPYSAMPLE(sk, line5); line5 += 8;
+
+		             sa = *line1; line1 += 8;
+		sc = *line2; line2 += 8; sd = *line2; line2 += 8;
+		             sf = *line3; line3 += 8;
+		sh = *line4; line4 += 8; si = *line4; line4 += 8;
+		             sk = *line5; line5 += 8;
+
+		outbyte = out + (y * width + x) * 4 + component;
+
+		for ( ; x < width - 3; x+=2)
+		{
+			int hd, vd, hp, vp;
+
+			//            SAMPLE2(sa, x-1, y-2); SAMPLE2(sb, x+1, y-2);
+			//SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x,   y-1); SAMPLE2(se, x+2, y-1);
+			//            SAMPLE2(sf, x-1, y  ); SAMPLE2(sg, x+1, y  );
+			//SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x,   y+1); SAMPLE2(sj, x+2, y+1);
+			//            SAMPLE2(sk, x-1, y+2); SAMPLE2(sl, x+1, y+2);
+
+			// optimization one
+			//SAMPLE2(sb, x+1, y-2);
+			//SAMPLE2(se, x+2, y-1);
+			//SAMPLE2(sg, x+1, y  );
+			//SAMPLE2(sj, x+2, y+1);
+			//SAMPLE2(sl, x+1, y+2);
+
+			//COPYSAMPLE(sb, line1); line1 += 8;
+			//COPYSAMPLE(se, line2); line2 += 8;
+			//COPYSAMPLE(sg, line3); line3 += 8;
+			//COPYSAMPLE(sj, line4); line4 += 8;
+			//COPYSAMPLE(sl, line5); line5 += 8;
+
+			sb = *line1; line1 += 8;
+			se = *line2; line2 += 8;
+			sg = *line3; line3 += 8;
+			sj = *line4; line4 += 8;
+			sl = *line5; line5 += 8;
+
+			hp = sf + sg; 
+			vp = sd + si;
+			hd = abs(sf - sg);
+			vd = abs(sd - si);
+
+			if (hd > 100 || vd > 100 || abs(hp-vp) > 200)
+			{
+				if (hd < vd)
+					*outbyte = hp >> 1;
+				else
+					*outbyte = vp >> 1;
+			}
+			else
+			{
+				int hdd, vdd;
+
+				//hdd = abs(sc[i] + sd[i] + se[i] - 3 * (sf[i] + sg[i]) + sh[i] + si[i] + sj[i]);
+				//vdd = abs(sa[i] + sf[i] + sk[i] - 3 * (sd[i] + si[i]) + sb[i] + sg[i] + sl[i]);
+
+				hdd = abs(sc + se - 3 * hp + sh + sj + vp);
+				vdd = abs(sa + sk - 3 * vp + sb + sl + hp);
+
+				if (hdd > vdd)
+					*outbyte = hp >> 1;
+				else 
+					*outbyte = vp >> 1;
+			}
+
+			outbyte += 8;
+
+			//          COPYSAMPLE(sa, sb);
+			//COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se);
+			//          COPYSAMPLE(sf, sg);
+			//COPYSAMPLE(sh, si); COPYSAMPLE(si, sj);
+			//          COPYSAMPLE(sk, sl);
+			    sa = sb;
+			sc = sd; sd = se;
+			    sf = sg;
+			sh = si; si = sj;
+			    sk = sl;
+		}
+	}
+}
+
+// Similar to FCBI, but throws out the second order derivatives for speed
+static void DoFCBIQuick(byte *in, byte *out, int width, int height, int component)
+{
+	int x, y;
+	byte *outbyte, *inbyte;
+
+	// copy in to out
+	for (y = 2; y < height - 2; y += 2)
+	{
+		inbyte  = in  + (y * width + 2) * 4 + component;
+		outbyte = out + (y * width + 2) * 4 + component;
+
+		for (x = 2; x < width - 2; x += 2)
+		{
+			*outbyte = *inbyte;
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+
+	for (y = 3; y < height - 4; y += 2)
+	{
+		byte sd, se, sh, si;
+		byte *line2, *line3;
+
+		x = 3;
+
+		line2 = in + ((y - 1) * width + (x - 1)) * 4 + component;
+		line3 = in + ((y + 1) * width + (x - 1)) * 4 + component;
+
+		sd = *line2; line2 += 8;
+		sh = *line3; line3 += 8;
+
+		outbyte = out + (y * width + x) * 4 + component;
+
+		for ( ; x < width - 4; x += 2)
+		{
+			int NWd, NEd, NWp, NEp;
+
+			se = *line2; line2 += 8;
+			si = *line3; line3 += 8;
+
+			NWp = sd + si;
+			NEp = se + sh;
+			NWd = abs(sd - si);
+			NEd = abs(se - sh);
+
+			if (NWd < NEd)
+				*outbyte = NWp >> 1;
+			else
+				*outbyte = NEp >> 1;
+
+			outbyte += 8;
+
+			sd = se;
+			sh = si;
+		}
+	}
+
+	// hack: copy out to in again
+	for (y = 3; y < height - 3; y += 2)
+	{
+		inbyte  = out + (y * width + 3) * 4 + component;
+		outbyte = in  + (y * width + 3) * 4 + component;
+
+		for (x = 3; x < width - 3; x += 2)
+		{
+			*outbyte = *inbyte;
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+	
+	for (y = 2; y < height - 3; y++)
+	{
+		byte sd, sf, sg, si;
+		byte *line2, *line3, *line4;
+
+		x = (y + 1) % 2 + 2;
+
+		line2 = in + ((y - 1) * width + (x    )) * 4 + component;
+		line3 = in + ((y    ) * width + (x - 1)) * 4 + component;
+		line4 = in + ((y + 1) * width + (x    )) * 4 + component;
+
+		outbyte = out + (y * width + x) * 4 + component;
+
+		sf = *line3; line3 += 8;
+
+		for ( ; x < width - 3; x+=2)
+		{
+			int hd, vd, hp, vp;
+
+			sd = *line2; line2 += 8;
+			sg = *line3; line3 += 8;
+			si = *line4; line4 += 8;
+			
+			hp = sf + sg; 
+			vp = sd + si;
+			hd = abs(sf - sg);
+			vd = abs(sd - si);
+
+			if (hd < vd)
+				*outbyte = hp >> 1;
+			else
+				*outbyte = vp >> 1;
+
+			outbyte += 8;
+
+			sf = sg;
+		}
+	}
+}
+
+// Similar to DoFCBIQuick, but just takes the average instead of checking derivatives
+// as well, this operates on all four components
+static void DoLinear(byte *in, byte *out, int width, int height)
+{
+	int x, y, i;
+	byte *outbyte, *inbyte;
+
+	// copy in to out
+	for (y = 2; y < height - 2; y += 2)
+	{
+		x = 2;
+
+		inbyte  = in  + (y * width + x) * 4;
+		outbyte = out + (y * width + x) * 4;
+
+		for ( ; x < width - 2; x += 2)
+		{
+			COPYSAMPLE(outbyte, inbyte);
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+
+	for (y = 1; y < height - 1; y += 2)
+	{
+		byte sd[4], se[4], sh[4], si[4];
+		byte *line2, *line3;
+
+		x = 1;
+
+		line2 = in + ((y - 1) * width + (x - 1)) * 4;
+		line3 = in + ((y + 1) * width + (x - 1)) * 4;
+
+		COPYSAMPLE(sd, line2); line2 += 8;
+		COPYSAMPLE(sh, line3); line3 += 8;
+
+		outbyte = out + (y * width + x) * 4;
+
+		for ( ; x < width - 1; x += 2)
+		{
+			COPYSAMPLE(se, line2); line2 += 8;
+			COPYSAMPLE(si, line3); line3 += 8;
+
+			for (i = 0; i < 4; i++)
+			{	
+				*outbyte++ = (sd[i] + si[i] + se[i] + sh[i]) >> 2;
+			}
+
+			outbyte += 4;
+
+			COPYSAMPLE(sd, se);
+			COPYSAMPLE(sh, si);
+		}
+	}
+
+	// hack: copy out to in again
+	for (y = 1; y < height - 1; y += 2)
+	{
+		x = 1;
+
+		inbyte  = out + (y * width + x) * 4;
+		outbyte = in  + (y * width + x) * 4;
+
+		for ( ; x < width - 1; x += 2)
+		{
+			COPYSAMPLE(outbyte, inbyte);
+			outbyte += 8;
+			inbyte += 8;
+		}
+	}
+	
+	for (y = 1; y < height - 1; y++)
+	{
+		byte sd[4], sf[4], sg[4], si[4];
+		byte *line2, *line3, *line4;
+
+		x = y % 2 + 1;
+
+		line2 = in + ((y - 1) * width + (x    )) * 4;
+		line3 = in + ((y    ) * width + (x - 1)) * 4;
+		line4 = in + ((y + 1) * width + (x    )) * 4;
+
+		COPYSAMPLE(sf, line3); line3 += 8;
+
+		outbyte = out + (y * width + x) * 4;
+
+		for ( ; x < width - 1; x += 2)
+		{
+			COPYSAMPLE(sd, line2); line2 += 8;
+			COPYSAMPLE(sg, line3); line3 += 8;
+			COPYSAMPLE(si, line4); line4 += 8;
+
+			for (i = 0; i < 4; i++)
+			{
+				*outbyte++ = (sf[i] + sg[i] + sd[i] + si[i]) >> 2;
+			}
+
+			outbyte += 4;
+
+			COPYSAMPLE(sf, sg);
+		}
+	}
+}
+
+
+static void ExpandHalfTextureToGrid( byte *data, int width, int height)
+{
+	int x, y;
+
+	for (y = height / 2; y > 0; y--)
+	{
+		byte *outbyte = data + ((y * 2 - 1) * (width)     - 2) * 4;
+		byte *inbyte  = data + (y           * (width / 2) - 1) * 4;
+
+		for (x = width / 2; x > 0; x--)
+		{
+			COPYSAMPLE(outbyte, inbyte);
+
+			outbyte -= 8;
+			inbyte -= 4;
+		}
+	}
+}
+
+static void FillInNormalizedZ(const byte *in, byte *out, int width, int height)
+{
+	int x, y;
+
+	for (y = 0; y < height; y++)
+	{
+		const byte *inbyte  = in  + y * width * 4;
+		byte       *outbyte = out + y * width * 4;
+
+		for (x = 0; x < width; x++)
+		{
+			byte nx, ny, nz, h;
+			float fnx, fny, fll, fnz;
+
+			nx = *inbyte++;
+			ny = *inbyte++;
+			inbyte++;
+			h  = *inbyte++;
+
+			fnx = OffsetByteToFloat(nx);
+			fny = OffsetByteToFloat(ny);
+			fll = 1.0f - fnx * fnx - fny * fny;
+			if (fll >= 0.0f)
+				fnz = (float)sqrt(fll);
+			else
+				fnz = 0.0f;
+
+			nz = FloatToOffsetByte(fnz);
+
+			*outbyte++ = nx;
+			*outbyte++ = ny;
+			*outbyte++ = nz;
+			*outbyte++ = h;
+		}
+	}
+}
+
+
+// size must be even
+#define WORKBLOCK_SIZE     128
+#define WORKBLOCK_BORDER   4
+#define WORKBLOCK_REALSIZE (WORKBLOCK_SIZE + WORKBLOCK_BORDER * 2)
+
+// assumes that data has already been expanded into a 2x2 grid
+static void FCBIByBlock(byte *data, int width, int height, qboolean clampToEdge, qboolean normalized)
+{
+	byte workdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4];
+	byte outdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4];
+	byte *inbyte, *outbyte;
+	int x, y;
+	int srcx, srcy;
+
+	ExpandHalfTextureToGrid(data, width, height);
+
+	for (y = 0; y < height; y += WORKBLOCK_SIZE)
+	{
+		for (x = 0; x < width; x += WORKBLOCK_SIZE)
+		{
+			int x2, y2;
+			int workwidth, workheight, fullworkwidth, fullworkheight;
+
+			workwidth =  MIN(WORKBLOCK_SIZE, width  - x);
+			workheight = MIN(WORKBLOCK_SIZE, height - y);
+
+			fullworkwidth =  workwidth  + WORKBLOCK_BORDER * 2;
+			fullworkheight = workheight + WORKBLOCK_BORDER * 2;
+
+			//memset(workdata, 0, WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4);
+
+			// fill in work block
+			for (y2 = 0; y2 < fullworkheight; y2 += 2)
+			{
+				srcy = y + y2 - WORKBLOCK_BORDER;
+
+				if (clampToEdge)
+				{
+					srcy = CLAMP(srcy, 0, height - 2);
+				}
+				else
+				{
+					srcy = (srcy + height) % height;
+				}
+
+				outbyte = workdata + y2   * fullworkwidth * 4;
+				inbyte  = data     + srcy * width         * 4;		
+
+				for (x2 = 0; x2 < fullworkwidth; x2 += 2)
+				{
+					srcx = x + x2 - WORKBLOCK_BORDER;
+
+					if (clampToEdge)
+					{
+						srcx = CLAMP(srcx, 0, width - 2);
+					}
+					else
+					{
+						srcx = (srcx + width) % width;
+					}
+
+					COPYSAMPLE(outbyte, inbyte + srcx * 4);
+					outbyte += 8;
+				}
+			}
+
+			// submit work block
+			DoLinear(workdata, outdata, fullworkwidth, fullworkheight);
+
+			if (!normalized)
+			{
+				switch (r_imageUpsampleType->integer)
+				{
+					case 0:
+						break;
+					case 1:
+						DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0);
+						break;
+					case 2:
+					default:
+						DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0);
+						break;
+				}
+			}
+			else
+			{
+				switch (r_imageUpsampleType->integer)
+				{
+					case 0:
+						break;
+					case 1:
+						DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0);
+						DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 1);
+						break;
+					case 2:
+					default:
+						DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0);
+						DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 1);
+						break;
+				}
+			}
+
+			// copy back work block
+			for (y2 = 0; y2 < workheight; y2++)
+			{
+				inbyte = outdata + ((y2 + WORKBLOCK_BORDER) * fullworkwidth + WORKBLOCK_BORDER) * 4;
+				outbyte = data +   ((y + y2)                * width         + x)                * 4;
+				for (x2 = 0; x2 < workwidth; x2++)
+				{
+					COPYSAMPLE(outbyte, inbyte);
+					outbyte += 4;
+					inbyte += 4;
+				}
+			}
+		}
+	}
+}
+#undef COPYSAMPLE
+
+/*
+================
+R_LightScaleTexture
+
+Scale up the pixel values in a texture to increase the
+lighting range
+================
+*/
+void R_LightScaleTexture (byte *in, int inwidth, int inheight, qboolean only_gamma )
+{
+	if ( only_gamma )
+	{
+		if ( !glConfig.deviceSupportsGamma )
+		{
+			int		i, c;
+			byte	*p;
+
+			p = in;
+
+			c = inwidth*inheight;
+			for (i=0 ; i<c ; i++, p+=4)
+			{
+				p[0] = s_gammatable[p[0]];
+				p[1] = s_gammatable[p[1]];
+				p[2] = s_gammatable[p[2]];
+			}
+		}
+	}
+	else
+	{
+		int		i, c;
+		byte	*p;
+
+		p = in;
+
+		c = inwidth*inheight;
+
+		if ( glConfig.deviceSupportsGamma )
+		{
+			for (i=0 ; i<c ; i++, p+=4)
+			{
+				p[0] = s_intensitytable[p[0]];
+				p[1] = s_intensitytable[p[1]];
+				p[2] = s_intensitytable[p[2]];
+			}
+		}
+		else
+		{
+			for (i=0 ; i<c ; i++, p+=4)
+			{
+				p[0] = s_gammatable[s_intensitytable[p[0]]];
+				p[1] = s_gammatable[s_intensitytable[p[1]]];
+				p[2] = s_gammatable[s_intensitytable[p[2]]];
+			}
+		}
+	}
+}
+
+
+/*
+================
+R_MipMap2
+
+Operates in place, quartering the size of the texture
+Proper linear filter
+================
+*/
+static void R_MipMap2( byte *in, int inWidth, int inHeight ) {
+	int			i, j, k;
+	byte		*outpix;
+	int			inWidthMask, inHeightMask;
+	int			total;
+	int			outWidth, outHeight;
+	unsigned	*temp;
+
+	outWidth = inWidth >> 1;
+	outHeight = inHeight >> 1;
+	temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
+
+	inWidthMask = inWidth - 1;
+	inHeightMask = inHeight - 1;
+
+	for ( i = 0 ; i < outHeight ; i++ ) {
+		for ( j = 0 ; j < outWidth ; j++ ) {
+			outpix = (byte *) ( temp + i * outWidth + j );
+			for ( k = 0 ; k < 4 ; k++ ) {
+				total = 
+					1 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2  )&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] +
+					1 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] +
+
+					2 * (&in[ 4*(((i*2  )&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] +
+					4 * (&in[ 4*(((i*2  )&inHeightMask)*inWidth + ((j*2  )&inWidthMask)) ])[k] +
+					4 * (&in[ 4*(((i*2  )&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2  )&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] +
+
+					2 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] +
+					4 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2  )&inWidthMask)) ])[k] +
+					4 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] +
+
+					1 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2  )&inWidthMask)) ])[k] +
+					2 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] +
+					1 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k];
+				outpix[k] = total / 36;
+			}
+		}
+	}
+
+	Com_Memcpy( in, temp, outWidth * outHeight * 4 );
+	ri.Hunk_FreeTempMemory( temp );
+}
+
+
+static void R_MipMapsRGB( byte *in, int inWidth, int inHeight)
+{
+	int			i, j, k;
+	int			outWidth, outHeight;
+	byte		*temp;
+
+	outWidth = inWidth >> 1;
+	outHeight = inHeight >> 1;
+	temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
+
+	for ( i = 0 ; i < outHeight ; i++ ) {
+		byte *outbyte = temp + (  i          * outWidth ) * 4;
+		byte *inbyte1 = in   + (  i * 2      * inWidth  ) * 4;
+		byte *inbyte2 = in   + ( (i * 2 + 1) * inWidth  ) * 4;
+		for ( j = 0 ; j < outWidth ; j++ ) {
+			for ( k = 0 ; k < 3 ; k++ ) {
+				float total, current;
+
+				current = ByteToFloat(inbyte1[0]); total  = sRGBtoRGB(current);
+				current = ByteToFloat(inbyte1[4]); total += sRGBtoRGB(current);
+				current = ByteToFloat(inbyte2[0]); total += sRGBtoRGB(current);
+				current = ByteToFloat(inbyte2[4]); total += sRGBtoRGB(current);
+
+				total *= 0.25f;
+
+				inbyte1++;
+				inbyte2++;
+
+				current = RGBtosRGB(total);
+				*outbyte++ = FloatToByte(current);
+			}
+			*outbyte++ = (inbyte1[0] + inbyte1[4] + inbyte2[0] + inbyte2[4]) >> 2;
+			inbyte1 += 5;
+			inbyte2 += 5;
+		}
+	}
+
+	Com_Memcpy( in, temp, outWidth * outHeight * 4 );
+	ri.Hunk_FreeTempMemory( temp );
+}
+
+/*
+================
+R_MipMap
+
+Operates in place, quartering the size of the texture
+================
+*/
+static void R_MipMap (byte *in, int width, int height) {
+	int		i, j;
+	byte	*out;
+	int		row;
+
+	if ( !r_simpleMipMaps->integer ) {
+		R_MipMap2( in, width, height );
+		return;
+	}
+
+	if ( width == 1 && height == 1 ) {
+		return;
+	}
+
+	row = width * 4;
+	out = in;
+	width >>= 1;
+	height >>= 1;
+
+	if ( width == 0 || height == 0 ) {
+		width += height;	// get largest
+		for (i=0 ; i<width ; i++, out+=4, in+=8 ) {
+			out[0] = ( in[0] + in[4] )>>1;
+			out[1] = ( in[1] + in[5] )>>1;
+			out[2] = ( in[2] + in[6] )>>1;
+			out[3] = ( in[3] + in[7] )>>1;
+		}
+		return;
+	}
+
+	for (i=0 ; i<height ; i++, in+=row) {
+		for (j=0 ; j<width ; j++, out+=4, in+=8) {
+			out[0] = (in[0] + in[4] + in[row+0] + in[row+4])>>2;
+			out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2;
+			out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2;
+			out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2;
+		}
+	}
+}
+
+
+static void R_MipMapLuminanceAlpha (const byte *in, byte *out, int width, int height)
+{
+	int  i, j, row;
+
+	if ( width == 1 && height == 1 ) {
+		return;
+	}
+
+	row = width * 4;
+	width >>= 1;
+	height >>= 1;
+
+	if ( width == 0 || height == 0 ) {
+		width += height;	// get largest
+		for (i=0 ; i<width ; i++, out+=4, in+=8 ) {
+			out[0] = 
+			out[1] = 
+			out[2] = (in[0] + in[4]) >> 1;
+			out[3] = (in[3] + in[7]) >> 1;
+		}
+		return;
+	}
+
+	for (i=0 ; i<height ; i++, in+=row) {
+		for (j=0 ; j<width ; j++, out+=4, in+=8) {
+			out[0] = 
+			out[1] = 
+			out[2] = (in[0] + in[4] + in[row  ] + in[row+4]) >> 2;
+			out[3] = (in[3] + in[7] + in[row+3] + in[row+7]) >> 2;
+		}
+	}
+
+}
+
+
+static void R_MipMapNormalHeight (const byte *in, byte *out, int width, int height, qboolean swizzle)
+{
+	int		i, j;
+	int		row;
+	int sx = swizzle ? 3 : 0;
+	int sa = swizzle ? 0 : 3;
+
+	if ( width == 1 && height == 1 ) {
+		return;
+	}
+
+	row = width * 4;
+	width >>= 1;
+	height >>= 1;
+	
+	for (i=0 ; i<height ; i++, in+=row) {
+		for (j=0 ; j<width ; j++, out+=4, in+=8) {
+			vec3_t v;
+
+			v[0] =  OffsetByteToFloat(in[sx      ]);
+			v[1] =  OffsetByteToFloat(in[       1]);
+			v[2] =  OffsetByteToFloat(in[       2]);
+
+			v[0] += OffsetByteToFloat(in[sx    +4]);
+			v[1] += OffsetByteToFloat(in[       5]);
+			v[2] += OffsetByteToFloat(in[       6]);
+
+			v[0] += OffsetByteToFloat(in[sx+row  ]);
+			v[1] += OffsetByteToFloat(in[   row+1]);
+			v[2] += OffsetByteToFloat(in[   row+2]);
+
+			v[0] += OffsetByteToFloat(in[sx+row+4]);
+			v[1] += OffsetByteToFloat(in[   row+5]);
+			v[2] += OffsetByteToFloat(in[   row+6]);
+
+			VectorNormalizeFast(v);
+
+			//v[0] *= 0.25f;
+			//v[1] *= 0.25f;
+			//v[2] = 1.0f - v[0] * v[0] - v[1] * v[1];
+			//v[2] = sqrt(MAX(v[2], 0.0f));
+
+			out[sx] = FloatToOffsetByte(v[0]);
+			out[1 ] = FloatToOffsetByte(v[1]);
+			out[2 ] = FloatToOffsetByte(v[2]);
+			out[sa] = MAX(MAX(in[sa], in[sa+4]), MAX(in[sa+row], in[sa+row+4]));
+		}
+	}
+}
+
+
+/*
+==================
+R_BlendOverTexture
+
+Apply a color blend over a set of pixels
+==================
+*/
+static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) {
+	int		i;
+	int		inverseAlpha;
+	int		premult[3];
+
+	inverseAlpha = 255 - blend[3];
+	premult[0] = blend[0] * blend[3];
+	premult[1] = blend[1] * blend[3];
+	premult[2] = blend[2] * blend[3];
+
+	for ( i = 0 ; i < pixelCount ; i++, data+=4 ) {
+		data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9;
+		data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9;
+		data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9;
+	}
+}
+
+byte	mipBlendColors[16][4] = {
+	{0,0,0,0},
+	{255,0,0,128},
+	{0,255,0,128},
+	{0,0,255,128},
+	{255,0,0,128},
+	{0,255,0,128},
+	{0,0,255,128},
+	{255,0,0,128},
+	{0,255,0,128},
+	{0,0,255,128},
+	{255,0,0,128},
+	{0,255,0,128},
+	{0,0,255,128},
+	{255,0,0,128},
+	{0,255,0,128},
+	{0,0,255,128},
+};
+
+static void RawImage_SwizzleRA( byte *data, int width, int height )
+{
+	int i;
+	byte *ptr = data, swap;
+
+	for (i=0; i<width*height; i++, ptr+=4)
+	{
+		// swap red and alpha
+		swap = ptr[0];
+		ptr[0] = ptr[3];
+		ptr[3] = swap;
+	}
+}
+
+
+/*
+===============
+RawImage_ScaleToPower2
+
+===============
+*/
+static void RawImage_ScaleToPower2( byte **data, int *inout_width, int *inout_height, int *inout_scaled_width, int *inout_scaled_height, imgType_t type, imgFlags_t flags, byte **resampledBuffer)
+{
+	int width =         *inout_width;
+	int height =        *inout_height;
+	int scaled_width =  *inout_scaled_width;
+	int scaled_height = *inout_scaled_height;
+	qboolean picmip = flags & IMGFLAG_PICMIP;
+	qboolean mipmap = flags & IMGFLAG_MIPMAP;
+	qboolean clampToEdge = flags & IMGFLAG_CLAMPTOEDGE;
+
+	//
+	// convert to exact power of 2 sizes
+	//
+	if (glRefConfig.textureNonPowerOfTwo && !mipmap)
+	{
+		scaled_width = width;
+		scaled_height = height;
+	}
+	else
+	{
+		scaled_width = NextPowerOfTwo(width);
+		scaled_height = NextPowerOfTwo(height);
+	}
+
+	if ( r_roundImagesDown->integer && scaled_width > width )
+		scaled_width >>= 1;
+	if ( r_roundImagesDown->integer && scaled_height > height )
+		scaled_height >>= 1;
+
+	if ( picmip && data && resampledBuffer && r_imageUpsample->integer && 
+	     scaled_width < r_imageUpsampleMaxSize->integer && scaled_height < r_imageUpsampleMaxSize->integer)
+	{
+		int finalwidth, finalheight;
+		//int startTime, endTime;
+
+		//startTime = ri.Milliseconds();
+
+		finalwidth = scaled_width << r_imageUpsample->integer;
+		finalheight = scaled_height << r_imageUpsample->integer;
+
+		while ( finalwidth > r_imageUpsampleMaxSize->integer
+			|| finalheight > r_imageUpsampleMaxSize->integer ) {
+			finalwidth >>= 1;
+			finalheight >>= 1;
+		}
+
+		while ( finalwidth > glConfig.maxTextureSize
+			|| finalheight > glConfig.maxTextureSize ) {
+			finalwidth >>= 1;
+			finalheight >>= 1;
+		}
+
+		*resampledBuffer = ri.Hunk_AllocateTempMemory( finalwidth * finalheight * 4 );
+
+		if (scaled_width != width || scaled_height != height)
+		{
+			ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height);
+		}
+		else
+		{
+			byte *inbyte, *outbyte;
+			int i;
+
+			inbyte = *data;
+			outbyte = *resampledBuffer;
+
+			for (i = width * height * 4; i > 0; i--)
+			{
+				*outbyte++ = *inbyte++;
+			}
+		}
+
+		if (type == IMGTYPE_COLORALPHA)
+			RGBAtoYCoCgA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height);
+
+		while (scaled_width < finalwidth || scaled_height < finalheight)
+		{
+			scaled_width <<= 1;
+			scaled_height <<= 1;
+
+			FCBIByBlock(*resampledBuffer, scaled_width, scaled_height, clampToEdge, (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT));
+		}
+
+		if (type == IMGTYPE_COLORALPHA)
+		{
+			YCoCgAtoRGBA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height);
+		}
+		else if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)
+		{
+			FillInNormalizedZ(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height);
+		}
+
+
+		//endTime = ri.Milliseconds();
+
+		//ri.Printf(PRINT_ALL, "upsampled %dx%d to %dx%d in %dms\n", width, height, scaled_width, scaled_height, endTime - startTime);
+
+		*data = *resampledBuffer;
+		width = scaled_width;
+		height = scaled_height;
+	}
+	else if ( scaled_width != width || scaled_height != height ) {
+		if (data && resampledBuffer)
+		{
+			*resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 );
+			ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height);
+			*data = *resampledBuffer;
+		}
+		width = scaled_width;
+		height = scaled_height;
+	}
+
+	//
+	// perform optional picmip operation
+	//
+	if ( picmip ) {
+		scaled_width >>= r_picmip->integer;
+		scaled_height >>= r_picmip->integer;
+	}
+
+	//
+	// clamp to minimum size
+	//
+	if (scaled_width < 1) {
+		scaled_width = 1;
+	}
+	if (scaled_height < 1) {
+		scaled_height = 1;
+	}
+
+	//
+	// clamp to the current upper OpenGL limit
+	// scale both axis down equally so we don't have to
+	// deal with a half mip resampling
+	//
+	while ( scaled_width > glConfig.maxTextureSize
+		|| scaled_height > glConfig.maxTextureSize ) {
+		scaled_width >>= 1;
+		scaled_height >>= 1;
+	}
+
+	*inout_width         = width;
+	*inout_height        = height;
+	*inout_scaled_width  = scaled_width;
+	*inout_scaled_height = scaled_height;
+}
+
+
+static qboolean RawImage_HasAlpha(const byte *scan, int numPixels)
+{
+	int i;
+
+	if (!scan)
+		return qtrue;
+
+	for ( i = 0; i < numPixels; i++ )
+	{
+		if ( scan[i*4 + 3] != 255 ) 
+		{
+			return qtrue;
+		}
+	}
+
+	return qfalse;
+}
+
+static GLenum RawImage_GetFormat(const byte *data, int numPixels, qboolean lightMap, imgType_t type, imgFlags_t flags)
+{
+	int samples = 3;
+	GLenum internalFormat = GL_RGB;
+	qboolean forceNoCompression = (flags & IMGFLAG_NO_COMPRESSION);
+	qboolean normalmap = (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT);
+
+	if(normalmap)
+	{
+		if ((!RawImage_HasAlpha(data, numPixels) || (type == IMGTYPE_NORMAL)) && !forceNoCompression && (glRefConfig.textureCompression & TCR_LATC))
+		{
+			internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+		}
+		else
+		{
+			if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB )
+			{
+				internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+			}
+			else if ( r_texturebits->integer == 16 )
+			{
+				internalFormat = GL_RGBA4;
+			}
+			else if ( r_texturebits->integer == 32 )
+			{
+				internalFormat = GL_RGBA8;
+			}
+			else
+			{
+				internalFormat = GL_RGBA;
+			}
+		}
+	}
+	else if(lightMap)
+	{
+		samples = 4;
+		if(r_greyscale->integer)
+			internalFormat = GL_LUMINANCE;
+		else
+			internalFormat = GL_RGBA;
+	}
+	else
+	{
+		if (RawImage_HasAlpha(data, numPixels))
+		{
+			samples = 4;
+		}
+
+		// select proper internal format
+		if ( samples == 3 )
+		{
+			if(r_greyscale->integer)
+			{
+				if(r_texturebits->integer == 16)
+					internalFormat = GL_LUMINANCE8;
+				else if(r_texturebits->integer == 32)
+					internalFormat = GL_LUMINANCE16;
+				else
+					internalFormat = GL_LUMINANCE;
+			}
+			else
+			{
+				if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) )
+				{
+					internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
+				}
+				else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB )
+				{
+					internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+				}
+				else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC )
+				{
+					internalFormat = GL_RGB4_S3TC;
+				}
+				else if ( r_texturebits->integer == 16 )
+				{
+					internalFormat = GL_RGB5;
+				}
+				else if ( r_texturebits->integer == 32 )
+				{
+					internalFormat = GL_RGB8;
+				}
+				else
+				{
+					internalFormat = GL_RGB;
+				}
+			}
+		}
+		else if ( samples == 4 )
+		{
+			if(r_greyscale->integer)
+			{
+				if(r_texturebits->integer == 16)
+					internalFormat = GL_LUMINANCE8_ALPHA8;
+				else if(r_texturebits->integer == 32)
+					internalFormat = GL_LUMINANCE16_ALPHA16;
+				else
+					internalFormat = GL_LUMINANCE_ALPHA;
+			}
+			else
+			{
+				if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) )
+				{
+					internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
+				}
+				else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB )
+				{
+					internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+				}
+				else if ( r_texturebits->integer == 16 )
+				{
+					internalFormat = GL_RGBA4;
+				}
+				else if ( r_texturebits->integer == 32 )
+				{
+					internalFormat = GL_RGBA8;
+				}
+				else
+				{
+					internalFormat = GL_RGBA;
+				}
+			}
+		}
+
+		if (glRefConfig.texture_srgb && (flags & IMGFLAG_SRGB))
+		{
+			switch(internalFormat)
+			{
+				case GL_RGB:
+					internalFormat = GL_SRGB_EXT;
+					break;
+
+				case GL_RGB4:
+				case GL_RGB5:
+				case GL_RGB8:
+					internalFormat = GL_SRGB8_EXT;
+					break;
+
+				case GL_RGBA:
+					internalFormat = GL_SRGB_ALPHA_EXT;
+					break;
+
+				case GL_RGBA4:
+				case GL_RGBA8:
+					internalFormat = GL_SRGB8_ALPHA8_EXT;
+					break;
+
+				case GL_LUMINANCE:
+					internalFormat = GL_SLUMINANCE_EXT;
+					break;
+
+				case GL_LUMINANCE8:
+				case GL_LUMINANCE16:
+					internalFormat = GL_SLUMINANCE8_EXT;
+					break;
+
+				case GL_LUMINANCE_ALPHA:
+					internalFormat = GL_SLUMINANCE_ALPHA_EXT;
+					break;
+
+				case GL_LUMINANCE8_ALPHA8:
+				case GL_LUMINANCE16_ALPHA16:
+					internalFormat = GL_SLUMINANCE8_ALPHA8_EXT;
+					break;
+
+				case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+					internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+					break;
+
+				case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+					internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+					break;
+
+				case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
+					internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
+					break;
+			}
+		}
+	}
+
+	return internalFormat;
+}
+
+
+static void RawImage_UploadTexture( byte *data, int x, int y, int width, int height, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture )
+{
+	int dataFormat, dataType;
+
+	switch(internalFormat)
+	{
+		case GL_DEPTH_COMPONENT:
+		case GL_DEPTH_COMPONENT16_ARB:
+		case GL_DEPTH_COMPONENT24_ARB:
+		case GL_DEPTH_COMPONENT32_ARB:
+			dataFormat = GL_DEPTH_COMPONENT;
+			dataType = GL_UNSIGNED_BYTE;
+			break;
+		case GL_RGBA16F_ARB:
+			dataFormat = GL_RGBA;
+			dataType = GL_HALF_FLOAT_ARB;
+			break;
+		default:
+			dataFormat = GL_RGBA;
+			dataType = GL_UNSIGNED_BYTE;
+			break;
+	}
+
+	if ( subtexture )
+		qglTexSubImage2D( GL_TEXTURE_2D, 0, x, y, width, height, dataFormat, dataType, data );
+	else
+		qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, width, height, 0, dataFormat, dataType, data );
+
+	if (flags & IMGFLAG_MIPMAP)
+	{
+		int miplevel;
+
+		miplevel = 0;
+		while (width > 1 || height > 1)
+		{
+			if (data)
+			{
+				if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)
+				{
+					if (internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT)
+					{
+						R_MipMapLuminanceAlpha( data, data, width, height );
+					}
+					else
+					{
+						R_MipMapNormalHeight( data, data, width, height, qtrue);
+					}
+				}
+				else if (flags & IMGFLAG_SRGB)
+				{
+					R_MipMapsRGB( data, width, height );
+				}
+				else
+				{
+					R_MipMap( data, width, height );
+				}
+			}
+			
+			width >>= 1;
+			height >>= 1;
+			if (width < 1)
+				width = 1;
+			if (height < 1)
+				height = 1;
+			miplevel++;
+
+			if ( data && r_colorMipLevels->integer )
+				R_BlendOverTexture( (byte *)data, width * height, mipBlendColors[miplevel] );
+
+			if ( subtexture )
+			{
+				x >>= 1;
+				y >>= 1;
+				qglTexSubImage2D( GL_TEXTURE_2D, miplevel, x, y, width, height, dataFormat, dataType, data );
+			}
+			else
+			{
+				qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, width, height, 0, dataFormat, dataType, data );
+			}
+		}
+	}
+}
+
+
+/*
+===============
+Upload32
+
+===============
+*/
+extern qboolean charSet;
+static void Upload32( byte *data, int width, int height, imgType_t type, imgFlags_t flags,
+	qboolean lightMap, GLenum internalFormat, int *pUploadWidth, int *pUploadHeight)
+{
+	byte		*scaledBuffer = NULL;
+	byte		*resampledBuffer = NULL;
+	int			scaled_width, scaled_height;
+	int			i, c;
+	byte		*scan;
+
+	RawImage_ScaleToPower2(&data, &width, &height, &scaled_width, &scaled_height, type, flags, &resampledBuffer);
+
+	scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height );
+
+	//
+	// scan the texture for each channel's max values
+	// and verify if the alpha channel is being used or not
+	//
+	c = width*height;
+	scan = data;
+	
+	if( r_greyscale->integer )
+	{
+		for ( i = 0; i < c; i++ )
+		{
+			byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]);
+			scan[i*4] = luma;
+			scan[i*4 + 1] = luma;
+			scan[i*4 + 2] = luma;
+		}
+	}
+	else if( r_greyscale->value )
+	{
+		for ( i = 0; i < c; i++ )
+		{
+			float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]);
+			scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value);
+			scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value);
+			scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value);
+		}
+	}
+
+	// normals are always swizzled
+	if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)
+	{
+		RawImage_SwizzleRA(data, width, height);
+	}
+
+	// LATC2 is only used for normals
+	if (internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT)
+	{
+		byte *in = data;
+		int c = width * height;
+		while (c--)
+		{
+			in[0] = in[1];
+			in[2] = in[1];
+			in += 4;
+		}
+	}
+
+	// copy or resample data as appropriate for first MIP level
+	if ( ( scaled_width == width ) && 
+		( scaled_height == height ) ) {
+		if (!(flags & IMGFLAG_MIPMAP))
+		{
+			RawImage_UploadTexture( data, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse );
+			//qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+			*pUploadWidth = scaled_width;
+			*pUploadHeight = scaled_height;
+
+			goto done;
+		}
+		Com_Memcpy (scaledBuffer, data, width*height*4);
+	}
+	else
+	{
+		// use the normal mip-mapping function to go down from here
+		while ( width > scaled_width || height > scaled_height ) {
+
+			if (flags & IMGFLAG_SRGB)
+			{
+				R_MipMapsRGB( (byte *)data, width, height );
+			}
+			else
+			{
+				R_MipMap( (byte *)data, width, height );
+			}
+
+			width >>= 1;
+			height >>= 1;
+			if ( width < 1 ) {
+				width = 1;
+			}
+			if ( height < 1 ) {
+				height = 1;
+			}
+		}
+		Com_Memcpy( scaledBuffer, data, width * height * 4 );
+	}
+
+	if (!(flags & IMGFLAG_NOLIGHTSCALE))
+		R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !(flags & IMGFLAG_MIPMAP) );
+
+	*pUploadWidth = scaled_width;
+	*pUploadHeight = scaled_height;
+
+	RawImage_UploadTexture(scaledBuffer, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse);
+
+done:
+
+	if (flags & IMGFLAG_MIPMAP)
+	{
+		if ( textureFilterAnisotropic )
+			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
+					(GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) );
+
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
+	}
+	else
+	{
+		if ( textureFilterAnisotropic )
+			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
+
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+	}
+
+	GL_CheckErrors();
+
+	if ( scaledBuffer != 0 )
+		ri.Hunk_FreeTempMemory( scaledBuffer );
+	if ( resampledBuffer != 0 )
+		ri.Hunk_FreeTempMemory( resampledBuffer );
+}
+
+
+static void EmptyTexture( int width, int height, imgType_t type, imgFlags_t flags,
+	qboolean lightMap, GLenum internalFormat, int *pUploadWidth, int *pUploadHeight )
+{
+	int			scaled_width, scaled_height;
+
+	RawImage_ScaleToPower2(NULL, &width, &height, &scaled_width, &scaled_height, type, flags, NULL);
+
+	*pUploadWidth = scaled_width;
+	*pUploadHeight = scaled_height;
+
+	RawImage_UploadTexture(NULL, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse);
+
+	if (flags & IMGFLAG_MIPMAP)
+	{
+		if ( textureFilterAnisotropic )
+			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
+					(GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) );
+
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
+	}
+	else
+	{
+		if ( textureFilterAnisotropic )
+			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
+
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+	}
+
+	// Fix for sampling depth buffer on old nVidia cards
+	// from http://www.idevgames.com/forums/thread-4141-post-34844.html#pid34844
+	switch(internalFormat)
+	{
+		case GL_DEPTH_COMPONENT:
+		case GL_DEPTH_COMPONENT16_ARB:
+		case GL_DEPTH_COMPONENT24_ARB:
+		case GL_DEPTH_COMPONENT32_ARB:
+			qglTexParameterf(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE );
+			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+			break;
+		default:
+			break;
+	}
+
+	GL_CheckErrors();
+}
+
+
+/*
+================
+R_CreateImage
+
+This is the only way any image_t are created
+================
+*/
+image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, imgFlags_t flags, int internalFormat ) {
+	image_t		*image;
+	qboolean	isLightmap = qfalse;
+	long		hash;
+	int         glWrapClampMode;
+
+	if (strlen(name) >= MAX_QPATH ) {
+		ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name);
+	}
+	if ( !strncmp( name, "*lightmap", 9 ) ) {
+		isLightmap = qtrue;
+	}
+
+	if ( tr.numImages == MAX_DRAWIMAGES ) {
+		ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit");
+	}
+
+	image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low );
+	image->texnum = 1024 + tr.numImages;
+	tr.numImages++;
+
+	image->type = type;
+	image->flags = flags;
+
+	strcpy (image->imgName, name);
+
+	image->width = width;
+	image->height = height;
+	if (flags & IMGFLAG_CLAMPTOEDGE)
+		glWrapClampMode = GL_CLAMP_TO_EDGE;
+	else
+		glWrapClampMode = GL_REPEAT;
+
+	if (!internalFormat)
+	{
+		if (image->flags & IMGFLAG_CUBEMAP)
+			internalFormat = GL_RGBA8;
+		else
+			internalFormat = RawImage_GetFormat(pic, width * height, isLightmap, image->type, image->flags);
+	}
+
+	image->internalFormat = internalFormat;
+		
+
+	// lightmaps are always allocated on TMU 1
+	if ( qglActiveTextureARB && isLightmap ) {
+		image->TMU = 1;
+	} else {
+		image->TMU = 0;
+	}
+
+	if ( qglActiveTextureARB ) {
+		GL_SelectTexture( image->TMU );
+	}
+
+	if (image->flags & IMGFLAG_CUBEMAP)
+	{
+		GL_BindCubemap(image);
+		qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+		qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+		qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic);
+
+		image->uploadWidth = width;
+		image->uploadHeight = height;
+	}
+	else
+	{
+		GL_Bind(image);
+
+		if (pic)
+		{
+			Upload32( pic, image->width, image->height, image->type, image->flags,
+				isLightmap, image->internalFormat, &image->uploadWidth,
+				&image->uploadHeight );
+		}
+		else
+		{
+			EmptyTexture(image->width, image->height, image->type, image->flags,
+				isLightmap, image->internalFormat, &image->uploadWidth,
+				&image->uploadHeight );
+		}
+
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode );
+		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode );
+	}
+
+	GL_SelectTexture( 0 );
+
+	hash = generateHashValue(name);
+	image->next = hashTable[hash];
+	hashTable[hash] = image;
+
+	return image;
+}
+
+void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height )
+{
+	byte *scaledBuffer = NULL;
+	byte *resampledBuffer = NULL;
+	int	 scaled_width, scaled_height, scaled_x, scaled_y;
+	byte *data = pic;
+
+	// normals are always swizzled
+	if (image->type == IMGTYPE_NORMAL || image->type == IMGTYPE_NORMALHEIGHT)
+	{
+		RawImage_SwizzleRA(pic, width, height);
+	}
+
+	// LATC2 is only used for normals
+	if (image->internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT)
+	{
+		byte *in = data;
+		int c = width * height;
+		while (c--)
+		{
+			in[0] = in[1];
+			in[2] = in[1];
+			in += 4;
+		}
+	}
+
+
+	RawImage_ScaleToPower2(&pic, &width, &height, &scaled_width, &scaled_height, image->type, image->flags, &resampledBuffer);
+
+	scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height );
+
+	if ( qglActiveTextureARB ) {
+		GL_SelectTexture( image->TMU );
+	}
+
+	GL_Bind(image);	
+
+	// copy or resample data as appropriate for first MIP level
+	if ( ( scaled_width == width ) && 
+		( scaled_height == height ) ) {
+		if (!(image->flags & IMGFLAG_MIPMAP))
+		{
+			scaled_x = x * scaled_width / width;
+			scaled_y = y * scaled_height / height;
+			RawImage_UploadTexture( data, scaled_x, scaled_y, scaled_width, scaled_height, image->internalFormat, image->type, image->flags, qtrue );
+			//qglTexSubImage2D( GL_TEXTURE_2D, 0, scaled_x, scaled_y, scaled_width, scaled_height, GL_RGBA, GL_UNSIGNED_BYTE, data );
+
+			GL_CheckErrors();
+			goto done;
+		}
+		Com_Memcpy (scaledBuffer, data, width*height*4);
+	}
+	else
+	{
+		// use the normal mip-mapping function to go down from here
+		while ( width > scaled_width || height > scaled_height ) {
+
+			if (image->flags & IMGFLAG_SRGB)
+			{
+				R_MipMapsRGB( (byte *)data, width, height );
+			}
+			else
+			{
+				R_MipMap( (byte *)data, width, height );
+			}
+
+			width >>= 1;
+			height >>= 1;
+			x >>= 1;
+			y >>= 1;
+			if ( width < 1 ) {
+				width = 1;
+			}
+			if ( height < 1 ) {
+				height = 1;
+			}
+		}
+		Com_Memcpy( scaledBuffer, data, width * height * 4 );
+	}
+
+	if (!(image->flags & IMGFLAG_NOLIGHTSCALE))
+		R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !(image->flags & IMGFLAG_MIPMAP) );
+
+	scaled_x = x * scaled_width / width;
+	scaled_y = y * scaled_height / height;
+	RawImage_UploadTexture( (byte *)data, scaled_x, scaled_y, scaled_width, scaled_height, image->internalFormat, image->type, image->flags, qtrue );
+
+done:
+	
+	GL_SelectTexture( 0 );
+
+	GL_CheckErrors();
+
+	if ( scaledBuffer != 0 )
+		ri.Hunk_FreeTempMemory( scaledBuffer );
+	if ( resampledBuffer != 0 )
+		ri.Hunk_FreeTempMemory( resampledBuffer );
+}
+
+//===================================================================
+
+typedef struct
+{
+	char *ext;
+	void (*ImageLoader)( const char *, unsigned char **, int *, int * );
+} imageExtToLoaderMap_t;
+
+// Note that the ordering indicates the order of preference used
+// when there are multiple images of different formats available
+static imageExtToLoaderMap_t imageLoaders[ ] =
+{
+	{ "tga",  R_LoadTGA },
+	{ "jpg",  R_LoadJPG },
+	{ "jpeg", R_LoadJPG },
+	{ "png",  R_LoadPNG },
+	{ "pcx",  R_LoadPCX },
+	{ "bmp",  R_LoadBMP }
+};
+
+static int numImageLoaders = ARRAY_LEN( imageLoaders );
+
+/*
+=================
+R_LoadImage
+
+Loads any of the supported image types into a cannonical
+32 bit format.
+=================
+*/
+void R_LoadImage( const char *name, byte **pic, int *width, int *height )
+{
+	qboolean orgNameFailed = qfalse;
+	int orgLoader = -1;
+	int i;
+	char localName[ MAX_QPATH ];
+	const char *ext;
+	char *altName;
+
+	*pic = NULL;
+	*width = 0;
+	*height = 0;
+
+	Q_strncpyz( localName, name, MAX_QPATH );
+
+	ext = COM_GetExtension( localName );
+
+	if( *ext )
+	{
+		// Look for the correct loader and use it
+		for( i = 0; i < numImageLoaders; i++ )
+		{
+			if( !Q_stricmp( ext, imageLoaders[ i ].ext ) )
+			{
+				// Load
+				imageLoaders[ i ].ImageLoader( localName, pic, width, height );
+				break;
+			}
+		}
+
+		// A loader was found
+		if( i < numImageLoaders )
+		{
+			if( *pic == NULL )
+			{
+				// Loader failed, most likely because the file isn't there;
+				// try again without the extension
+				orgNameFailed = qtrue;
+				orgLoader = i;
+				COM_StripExtension( name, localName, MAX_QPATH );
+			}
+			else
+			{
+				// Something loaded
+				return;
+			}
+		}
+	}
+
+	// Try and find a suitable match using all
+	// the image formats supported
+	for( i = 0; i < numImageLoaders; i++ )
+	{
+		if (i == orgLoader)
+			continue;
+
+		altName = va( "%s.%s", localName, imageLoaders[ i ].ext );
+
+		// Load
+		imageLoaders[ i ].ImageLoader( altName, pic, width, height );
+
+		if( *pic )
+		{
+			if( orgNameFailed )
+			{
+				ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n",
+						name, altName );
+			}
+
+			break;
+		}
+	}
+}
+
+
+/*
+===============
+R_FindImageFile
+
+Finds or loads the given image.
+Returns NULL if it fails, not a default image.
+==============
+*/
+image_t	*R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags )
+{
+	image_t	*image;
+	int		width, height;
+	byte	*pic;
+	long	hash;
+
+	if (!name) {
+		return NULL;
+	}
+
+	hash = generateHashValue(name);
+
+	//
+	// see if the image is already loaded
+	//
+	for (image=hashTable[hash]; image; image=image->next) {
+		if ( !strcmp( name, image->imgName ) ) {
+			// the white image can be used with any set of parms, but other mismatches are errors
+			if ( strcmp( name, "*white" ) ) {
+				if ( image->flags != flags ) {
+					ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags );
+				}
+			}
+			return image;
+		}
+	}
+
+	//
+	// load the pic from disk
+	//
+	R_LoadImage( name, &pic, &width, &height );
+	if ( pic == NULL ) {
+		return NULL;
+	}
+
+	if (r_normalMapping->integer && !(type == IMGTYPE_NORMAL) && (flags & IMGFLAG_PICMIP) && (flags & IMGFLAG_MIPMAP) && (flags & IMGFLAG_GENNORMALMAP))
+	{
+		char normalName[MAX_QPATH];
+		image_t *normalImage;
+		int normalWidth, normalHeight;
+		imgFlags_t normalFlags;
+
+		normalFlags = (flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE;
+
+		COM_StripExtension(name, normalName, MAX_QPATH);
+		Q_strcat(normalName, MAX_QPATH, "_n");
+
+		// find normalmap in case it's there
+		normalImage = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags);
+
+		// if not, generate it
+		if (normalImage == NULL)
+		{
+			byte *normalPic;
+			int x, y;
+
+			normalWidth = width;
+			normalHeight = height;
+			normalPic = ri.Malloc(width * height * 4);
+			RGBAtoNormal(pic, normalPic, width, height, flags & IMGFLAG_CLAMPTOEDGE);
+
+			// Brighten up the original image to work with the normal map
+			RGBAtoYCoCgA(pic, pic, width, height);
+			for (y = 0; y < height; y++)
+			{
+				byte *picbyte  = pic       + y * width * 4;
+				byte *normbyte = normalPic + y * width * 4;
+				for (x = 0; x < width; x++)
+				{
+					int div = MAX(normbyte[2] - 127, 16);
+					picbyte[0] = CLAMP(picbyte[0] * 128 / div, 0, 255);
+					picbyte  += 4;
+					normbyte += 4;
+				}
+			}
+			YCoCgAtoRGBA(pic, pic, width, height);
+
+			R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 );
+			ri.Free( normalPic );	
+		}
+	}
+
+	image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 );
+	ri.Free( pic );
+	return image;
+}
+
+
+/*
+================
+R_CreateDlightImage
+================
+*/
+#define	DLIGHT_SIZE	16
+static void R_CreateDlightImage( void ) {
+	int		x,y;
+	byte	data[DLIGHT_SIZE][DLIGHT_SIZE][4];
+	int		b;
+
+	// make a centered inverse-square falloff blob for dynamic lighting
+	for (x=0 ; x<DLIGHT_SIZE ; x++) {
+		for (y=0 ; y<DLIGHT_SIZE ; y++) {
+			float	d;
+
+			d = ( DLIGHT_SIZE/2 - 0.5f - x ) * ( DLIGHT_SIZE/2 - 0.5f - x ) +
+				( DLIGHT_SIZE/2 - 0.5f - y ) * ( DLIGHT_SIZE/2 - 0.5f - y );
+			b = 4000 / d;
+			if (b > 255) {
+				b = 255;
+			} else if ( b < 75 ) {
+				b = 0;
+			}
+			data[y][x][0] = 
+			data[y][x][1] = 
+			data[y][x][2] = b;
+			data[y][x][3] = 255;			
+		}
+	}
+	tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 );
+}
+
+
+/*
+=================
+R_InitFogTable
+=================
+*/
+void R_InitFogTable( void ) {
+	int		i;
+	float	d;
+	float	exp;
+	
+	exp = 0.5;
+
+	for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) {
+		d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp );
+
+		tr.fogTable[i] = d;
+	}
+}
+
+/*
+================
+R_FogFactor
+
+Returns a 0.0 to 1.0 fog density value
+This is called for each texel of the fog texture on startup
+and for each vertex of transparent shaders in fog dynamically
+================
+*/
+float	R_FogFactor( float s, float t ) {
+	float	d;
+
+	s -= 1.0/512;
+	if ( s < 0 ) {
+		return 0;
+	}
+	if ( t < 1.0/32 ) {
+		return 0;
+	}
+	if ( t < 31.0/32 ) {
+		s *= (t - 1.0f/32.0f) / (30.0f/32.0f);
+	}
+
+	// we need to leave a lot of clamp range
+	s *= 8;
+
+	if ( s > 1.0 ) {
+		s = 1.0;
+	}
+
+	d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ];
+
+	return d;
+}
+
+/*
+================
+R_CreateFogImage
+================
+*/
+#define	FOG_S	256
+#define	FOG_T	32
+static void R_CreateFogImage( void ) {
+	int		x,y;
+	byte	*data;
+	float	d;
+	float	borderColor[4];
+
+	data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 );
+
+	// S is distance, T is depth
+	for (x=0 ; x<FOG_S ; x++) {
+		for (y=0 ; y<FOG_T ; y++) {
+			d = R_FogFactor( ( x + 0.5f ) / FOG_S, ( y + 0.5f ) / FOG_T );
+
+			data[(y*FOG_S+x)*4+0] = 
+			data[(y*FOG_S+x)*4+1] = 
+			data[(y*FOG_S+x)*4+2] = 255;
+			data[(y*FOG_S+x)*4+3] = 255*d;
+		}
+	}
+	// standard openGL clamping doesn't really do what we want -- it includes
+	// the border color at the edges.  OpenGL 1.2 has clamp-to-edge, which does
+	// what we want.
+	tr.fogImage = R_CreateImage("*fog", (byte *)data, FOG_S, FOG_T, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 );
+	ri.Hunk_FreeTempMemory( data );
+
+	borderColor[0] = 1.0;
+	borderColor[1] = 1.0;
+	borderColor[2] = 1.0;
+	borderColor[3] = 1;
+
+	qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );
+}
+
+/*
+==================
+R_CreateDefaultImage
+==================
+*/
+#define	DEFAULT_SIZE	16
+static void R_CreateDefaultImage( void ) {
+	int		x;
+	byte	data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+	// the default image will be a box, to allow you to see the mapping coordinates
+	Com_Memset( data, 32, sizeof( data ) );
+	for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) {
+		data[0][x][0] =
+		data[0][x][1] =
+		data[0][x][2] =
+		data[0][x][3] = 255;
+
+		data[x][0][0] =
+		data[x][0][1] =
+		data[x][0][2] =
+		data[x][0][3] = 255;
+
+		data[DEFAULT_SIZE-1][x][0] =
+		data[DEFAULT_SIZE-1][x][1] =
+		data[DEFAULT_SIZE-1][x][2] =
+		data[DEFAULT_SIZE-1][x][3] = 255;
+
+		data[x][DEFAULT_SIZE-1][0] =
+		data[x][DEFAULT_SIZE-1][1] =
+		data[x][DEFAULT_SIZE-1][2] =
+		data[x][DEFAULT_SIZE-1][3] = 255;
+	}
+	tr.defaultImage = R_CreateImage("*default", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_MIPMAP, 0);
+}
+
+/*
+==================
+R_CreateBuiltinImages
+==================
+*/
+void R_CreateBuiltinImages( void ) {
+	int		x,y;
+	byte	data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+	R_CreateDefaultImage();
+
+	// we use a solid white image instead of disabling texturing
+	Com_Memset( data, 255, sizeof( data ) );
+	tr.whiteImage = R_CreateImage("*white", (byte *)data, 8, 8, IMGTYPE_COLORALPHA, IMGFLAG_NONE, 0);
+
+	if (r_dlightMode->integer >= 2)
+	{
+		for( x = 0; x < MAX_DLIGHTS; x++)
+		{
+			tr.shadowCubemaps[x] = R_CreateImage(va("*shadowcubemap%i", x), (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE | IMGFLAG_CUBEMAP, 0);
+		}
+	}
+
+	// with overbright bits active, we need an image which is some fraction of full color,
+	// for default lightmaps, etc
+	for (x=0 ; x<DEFAULT_SIZE ; x++) {
+		for (y=0 ; y<DEFAULT_SIZE ; y++) {
+			data[y][x][0] = 
+			data[y][x][1] = 
+			data[y][x][2] = tr.identityLightByte;
+			data[y][x][3] = 255;			
+		}
+	}
+
+	tr.identityLightImage = R_CreateImage("*identityLight", (byte *)data, 8, 8, IMGTYPE_COLORALPHA, IMGFLAG_NONE, 0);
+
+
+	for(x=0;x<32;x++) {
+		// scratchimage is usually used for cinematic drawing
+		tr.scratchImage[x] = R_CreateImage("*scratch", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_PICMIP | IMGFLAG_CLAMPTOEDGE, 0);
+	}
+
+	R_CreateDlightImage();
+	R_CreateFogImage();
+
+	{
+		int width, height, hdrFormat;
+
+		if(glRefConfig.textureNonPowerOfTwo)
+		{
+			width = glConfig.vidWidth;
+			height = glConfig.vidHeight;
+		}
+		else
+		{
+			width = NextPowerOfTwo(glConfig.vidWidth);
+			height = NextPowerOfTwo(glConfig.vidHeight);
+		}
+
+		hdrFormat = GL_RGBA8;
+		if (r_hdr->integer && glRefConfig.framebufferObject && glRefConfig.textureFloat)
+			hdrFormat = GL_RGB16F_ARB;
+
+		tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
+#ifdef REACTION
+		tr.godRaysImage = R_CreateImage("*godRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+#endif
+
+		{
+			int format;
+
+			if (glRefConfig.texture_srgb && glRefConfig.framebuffer_srgb)
+				format = GL_SRGB8_ALPHA8_EXT;
+			else
+				format = GL_RGBA8;
+
+			tr.screenScratchImage = R_CreateImage("*screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, format);
+		}
+
+		if (glRefConfig.framebufferObject)
+		{
+			tr.renderDepthImage  = R_CreateImage("*renderdepth",  NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB);
+			tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB);
+		}
+
+		{
+			unsigned short sdata[4];
+			void *p;
+
+			if (hdrFormat == GL_RGB16F_ARB)
+			{
+				sdata[0] = FloatToHalf(0.0f);
+				sdata[1] = FloatToHalf(0.45f);
+				sdata[2] = FloatToHalf(1.0f);
+				sdata[3] = FloatToHalf(1.0f);
+				p = &sdata[0];
+			}
+			else
+			{
+				data[0][0][0] = 0;
+				data[0][0][1] = 0.45f * 255;
+				data[0][0][2] = 255;
+				data[0][0][3] = 255;
+				p = data;
+			}
+
+			tr.calcLevelsImage =   R_CreateImage("*calcLevels",    p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
+			tr.targetLevelsImage = R_CreateImage("*targetLevels",  p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
+			tr.fixedLevelsImage =  R_CreateImage("*fixedLevels",   p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
+		}
+
+		for (x = 0; x < 2; x++)
+		{
+			tr.textureScratchImage[x] = R_CreateImage(va("*textureScratch%d", x), NULL, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+		}
+		for (x = 0; x < 2; x++)
+		{
+			tr.quarterImage[x] = R_CreateImage(va("*quarter%d", x), NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+		}
+
+		tr.screenShadowImage = R_CreateImage("*screenShadow", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+
+		if (r_ssao->integer)
+		{
+			tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+			tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB);
+		}
+	}
+
+	for( x = 0; x < MAX_DRAWN_PSHADOWS; x++)
+	{
+		tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+	}
+
+	//tr.sunShadowImage = R_CreateImage("*sunshadowmap", NULL, SUNSHADOW_MAP_SIZE, SUNSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+	for ( x = 0; x < 3; x++)
+	{
+		tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x),  NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB);
+	}
+}
+
+
+/*
+===============
+R_SetColorMappings
+===============
+*/
+void R_SetColorMappings( void ) {
+	int		i, j;
+	float	g;
+	int		inf;
+	int		shift;
+
+	// setup the overbright lighting
+	tr.overbrightBits = r_overBrightBits->integer;
+	if ( !glConfig.deviceSupportsGamma ) {
+		tr.overbrightBits = 0;		// need hardware gamma for overbright
+	}
+
+	// never overbright in windowed mode
+	if ( 0 /* !glConfig.isFullscreen */ ) 
+	{
+		tr.overbrightBits = 0;
+	}
+
+	// never overbright with tonemapping
+	if ( r_toneMap->integer )
+	{
+		tr.overbrightBits = 0;
+	}
+
+	// allow 2 overbright bits in 24 bit, but only 1 in 16 bit
+	if ( glConfig.colorBits > 16 ) {
+		if ( tr.overbrightBits > 2 ) {
+			tr.overbrightBits = 2;
+		}
+	} else {
+		if ( tr.overbrightBits > 1 ) {
+			tr.overbrightBits = 1;
+		}
+	}
+	if ( tr.overbrightBits < 0 ) {
+		tr.overbrightBits = 0;
+	}
+
+	tr.identityLight = 1.0f / ( 1 << tr.overbrightBits );
+	tr.identityLightByte = 255 * tr.identityLight;
+
+
+	if ( r_intensity->value <= 1 ) {
+		ri.Cvar_Set( "r_intensity", "1" );
+	}
+
+	if ( r_gamma->value < 0.5f ) {
+		ri.Cvar_Set( "r_gamma", "0.5" );
+	} else if ( r_gamma->value > 3.0f ) {
+		ri.Cvar_Set( "r_gamma", "3.0" );
+	}
+
+	g = r_gamma->value;
+
+	shift = tr.overbrightBits;
+
+	if (glRefConfig.framebufferObject)
+		shift = 0;
+
+	for ( i = 0; i < 256; i++ ) {
+		if ( g == 1 ) {
+			inf = i;
+		} else {
+			inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f;
+		}
+		inf <<= shift;
+		if (inf < 0) {
+			inf = 0;
+		}
+		if (inf > 255) {
+			inf = 255;
+		}
+		s_gammatable[i] = inf;
+	}
+
+	for (i=0 ; i<256 ; i++) {
+		j = i * r_intensity->value;
+		if (j > 255) {
+			j = 255;
+		}
+		s_intensitytable[i] = j;
+	}
+
+	if ( glConfig.deviceSupportsGamma )
+	{
+		GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable );
+	}
+}
+
+/*
+===============
+R_InitImages
+===============
+*/
+void	R_InitImages( void ) {
+	Com_Memset(hashTable, 0, sizeof(hashTable));
+	// build brightness translation tables
+	R_SetColorMappings();
+
+	// create default texture and white texture
+	R_CreateBuiltinImages();
+}
+
+/*
+===============
+R_DeleteTextures
+===============
+*/
+void R_DeleteTextures( void ) {
+	int		i;
+
+	for ( i=0; i<tr.numImages ; i++ ) {
+		qglDeleteTextures( 1, &tr.images[i]->texnum );
+	}
+	Com_Memset( tr.images, 0, sizeof( tr.images ) );
+
+	tr.numImages = 0;
+
+	Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) );
+	if ( qglActiveTextureARB ) {
+		GL_SelectTexture( 1 );
+		qglBindTexture( GL_TEXTURE_2D, 0 );
+		GL_SelectTexture( 0 );
+		qglBindTexture( GL_TEXTURE_2D, 0 );
+	} else {
+		qglBindTexture( GL_TEXTURE_2D, 0 );
+	}
+}
+
+/*
+============================================================================
+
+SKINS
+
+============================================================================
+*/
+
+/*
+==================
+CommaParse
+
+This is unfortunate, but the skin files aren't
+compatable with our normal parsing rules.
+==================
+*/
+static char *CommaParse( char **data_p ) {
+	int c = 0, len;
+	char *data;
+	static	char	com_token[MAX_TOKEN_CHARS];
+
+	data = *data_p;
+	len = 0;
+	com_token[0] = 0;
+
+	// make sure incoming data is valid
+	if ( !data ) {
+		*data_p = NULL;
+		return com_token;
+	}
+
+	while ( 1 ) {
+		// skip whitespace
+		while( (c = *data) <= ' ') {
+			if( !c ) {
+				break;
+			}
+			data++;
+		}
+
+
+		c = *data;
+
+		// skip double slash comments
+		if ( c == '/' && data[1] == '/' )
+		{
+			while (*data && *data != '\n')
+				data++;
+		}
+		// skip /* */ comments
+		else if ( c=='/' && data[1] == '*' ) 
+		{
+			while ( *data && ( *data != '*' || data[1] != '/' ) ) 
+			{
+				data++;
+			}
+			if ( *data ) 
+			{
+				data += 2;
+			}
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	if ( c == 0 ) {
+		return "";
+	}
+
+	// handle quoted strings
+	if (c == '\"')
+	{
+		data++;
+		while (1)
+		{
+			c = *data++;
+			if (c=='\"' || !c)
+			{
+				com_token[len] = 0;
+				*data_p = ( char * ) data;
+				return com_token;
+			}
+			if (len < MAX_TOKEN_CHARS)
+			{
+				com_token[len] = c;
+				len++;
+			}
+		}
+	}
+
+	// parse a regular word
+	do
+	{
+		if (len < MAX_TOKEN_CHARS)
+		{
+			com_token[len] = c;
+			len++;
+		}
+		data++;
+		c = *data;
+	} while (c>32 && c != ',' );
+
+	if (len == MAX_TOKEN_CHARS)
+	{
+//		ri.Printf (PRINT_DEVELOPER, "Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
+		len = 0;
+	}
+	com_token[len] = 0;
+
+	*data_p = ( char * ) data;
+	return com_token;
+}
+
+
+/*
+===============
+RE_RegisterSkin
+
+===============
+*/
+qhandle_t RE_RegisterSkin( const char *name ) {
+	qhandle_t	hSkin;
+	skin_t		*skin;
+	skinSurface_t	*surf;
+	union {
+		char *c;
+		void *v;
+	} text;
+	char		*text_p;
+	char		*token;
+	char		surfName[MAX_QPATH];
+
+	if ( !name || !name[0] ) {
+		ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" );
+		return 0;
+	}
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		ri.Printf( PRINT_DEVELOPER, "Skin name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+
+	// see if the skin is already loaded
+	for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) {
+		skin = tr.skins[hSkin];
+		if ( !Q_stricmp( skin->name, name ) ) {
+			if( skin->numSurfaces == 0 ) {
+				return 0;		// default skin
+			}
+			return hSkin;
+		}
+	}
+
+	// allocate a new skin
+	if ( tr.numSkins == MAX_SKINS ) {
+		ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name );
+		return 0;
+	}
+	tr.numSkins++;
+	skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+	tr.skins[hSkin] = skin;
+	Q_strncpyz( skin->name, name, sizeof( skin->name ) );
+	skin->numSurfaces = 0;
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	// If not a .skin file, load as a single shader
+	if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) {
+		skin->numSurfaces = 1;
+		skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low );
+		skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue );
+		return hSkin;
+	}
+
+	// load and parse the skin file
+    ri.FS_ReadFile( name, &text.v );
+	if ( !text.c ) {
+		return 0;
+	}
+
+	text_p = text.c;
+	while ( text_p && *text_p ) {
+		// get surface name
+		token = CommaParse( &text_p );
+		Q_strncpyz( surfName, token, sizeof( surfName ) );
+
+		if ( !token[0] ) {
+			break;
+		}
+		// lowercase the surface name so skin compares are faster
+		Q_strlwr( surfName );
+
+		if ( *text_p == ',' ) {
+			text_p++;
+		}
+
+		if ( strstr( token, "tag_" ) ) {
+			continue;
+		}
+		
+		// parse the shader name
+		token = CommaParse( &text_p );
+
+		surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low );
+		Q_strncpyz( surf->name, surfName, sizeof( surf->name ) );
+		surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue );
+		skin->numSurfaces++;
+	}
+
+	ri.FS_FreeFile( text.v );
+
+
+	// never let a skin have 0 shaders
+	if ( skin->numSurfaces == 0 ) {
+		return 0;		// use default skin
+	}
+
+	return hSkin;
+}
+
+
+/*
+===============
+R_InitSkins
+===============
+*/
+void	R_InitSkins( void ) {
+	skin_t		*skin;
+
+	tr.numSkins = 1;
+
+	// make the default skin have all default shaders
+	skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+	Q_strncpyz( skin->name, "<default skin>", sizeof( skin->name )  );
+	skin->numSurfaces = 1;
+	skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low );
+	skin->surfaces[0]->shader = tr.defaultShader;
+}
+
+/*
+===============
+R_GetSkinByHandle
+===============
+*/
+skin_t	*R_GetSkinByHandle( qhandle_t hSkin ) {
+	if ( hSkin < 1 || hSkin >= tr.numSkins ) {
+		return tr.skins[0];
+	}
+	return tr.skins[ hSkin ];
+}
+
+/*
+===============
+R_SkinList_f
+===============
+*/
+void	R_SkinList_f( void ) {
+	int			i, j;
+	skin_t		*skin;
+
+	ri.Printf (PRINT_ALL, "------------------\n");
+
+	for ( i = 0 ; i < tr.numSkins ; i++ ) {
+		skin = tr.skins[i];
+
+		ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name );
+		for ( j = 0 ; j < skin->numSurfaces ; j++ ) {
+			ri.Printf( PRINT_ALL, "       %s = %s\n", 
+				skin->surfaces[j]->name, skin->surfaces[j]->shader->name );
+		}
+	}
+	ri.Printf (PRINT_ALL, "------------------\n");
+}
+
+

Added: trunk/code/rend2/tr_image_bmp.c
===================================================================
--- trunk/code/rend2/tr_image_bmp.c	                        (rev 0)
+++ trunk/code/rend2/tr_image_bmp.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,243 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+extern	refimport_t		ri;
+
+typedef struct
+{
+	char id[2];
+	unsigned fileSize;
+	unsigned reserved0;
+	unsigned bitmapDataOffset;
+	unsigned bitmapHeaderSize;
+	unsigned width;
+	unsigned height;
+	unsigned short planes;
+	unsigned short bitsPerPixel;
+	unsigned compression;
+	unsigned bitmapDataSize;
+	unsigned hRes;
+	unsigned vRes;
+	unsigned colors;
+	unsigned importantColors;
+	unsigned char palette[256][4];
+} BMPHeader_t;
+
+void R_LoadBMP( const char *name, byte **pic, int *width, int *height )
+{
+	int		columns, rows;
+	unsigned	numPixels;
+	byte	*pixbuf;
+	int		row, column;
+	byte	*buf_p;
+	byte	*end;
+	union {
+		byte *b;
+		void *v;
+	} buffer;
+	int		length;
+	BMPHeader_t bmpHeader;
+	byte		*bmpRGBA;
+
+	*pic = NULL;
+
+	if(width)
+		*width = 0;
+
+	if(height)
+		*height = 0;
+
+	//
+	// load the file
+	//
+	length = ri.FS_ReadFile( ( char * ) name, &buffer.v);
+	if (!buffer.b || length < 0) {
+		return;
+	}
+
+	if (length < 54)
+	{
+		ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name );
+	}
+
+	buf_p = buffer.b;
+	end = buffer.b + length;
+
+	bmpHeader.id[0] = *buf_p++;
+	bmpHeader.id[1] = *buf_p++;
+	bmpHeader.fileSize = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.width = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.height = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.planes = LittleShort( * ( short * ) buf_p );
+	buf_p += 2;
+	bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p );
+	buf_p += 2;
+	bmpHeader.compression = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.hRes = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.vRes = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.colors = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+	bmpHeader.importantColors = LittleLong( * ( int * ) buf_p );
+	buf_p += 4;
+
+	if ( bmpHeader.bitsPerPixel == 8 )
+	{
+		if (buf_p + sizeof(bmpHeader.palette) > end)
+			ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name );
+
+		Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );
+		buf_p += sizeof(bmpHeader.palette);
+	}
+
+	if (buffer.b + bmpHeader.bitmapDataOffset > end)
+	{
+		ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)", name );
+	}
+
+	buf_p = buffer.b + bmpHeader.bitmapDataOffset;
+
+	if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) 
+	{
+		ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)", name );
+	}
+	if ( bmpHeader.fileSize != length )
+	{
+		ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%u vs. %u) (%s)", bmpHeader.fileSize, length, name );
+	}
+	if ( bmpHeader.compression != 0 )
+	{
+		ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)", name );
+	}
+	if ( bmpHeader.bitsPerPixel < 8 )
+	{
+		ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)", name );
+	}
+
+	switch ( bmpHeader.bitsPerPixel )
+	{
+		case 8:
+		case 16:
+		case 24:
+		case 32:
+			break;
+		default:
+			ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'", bmpHeader.bitsPerPixel, name );
+			break;
+	}
+
+	columns = bmpHeader.width;
+	rows = bmpHeader.height;
+	if ( rows < 0 )
+		rows = -rows;
+	numPixels = columns * rows;
+
+	if(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF
+	    || ((numPixels * 4) / columns) / 4 != rows)
+	{
+	  ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size", name);
+	}
+	if(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end)
+	{
+	  ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)", name);
+	}
+
+	if ( width ) 
+		*width = columns;
+	if ( height )
+		*height = rows;
+
+	bmpRGBA = ri.Malloc( numPixels * 4 );
+	*pic = bmpRGBA;
+
+
+	for ( row = rows-1; row >= 0; row-- )
+	{
+		pixbuf = bmpRGBA + row*columns*4;
+
+		for ( column = 0; column < columns; column++ )
+		{
+			unsigned char red, green, blue, alpha;
+			int palIndex;
+			unsigned short shortPixel;
+
+			switch ( bmpHeader.bitsPerPixel )
+			{
+			case 8:
+				palIndex = *buf_p++;
+				*pixbuf++ = bmpHeader.palette[palIndex][2];
+				*pixbuf++ = bmpHeader.palette[palIndex][1];
+				*pixbuf++ = bmpHeader.palette[palIndex][0];
+				*pixbuf++ = 0xff;
+				break;
+			case 16:
+				shortPixel = * ( unsigned short * ) pixbuf;
+				pixbuf += 2;
+				*pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
+				*pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
+				*pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
+				*pixbuf++ = 0xff;
+				break;
+
+			case 24:
+				blue = *buf_p++;
+				green = *buf_p++;
+				red = *buf_p++;
+				*pixbuf++ = red;
+				*pixbuf++ = green;
+				*pixbuf++ = blue;
+				*pixbuf++ = 255;
+				break;
+			case 32:
+				blue = *buf_p++;
+				green = *buf_p++;
+				red = *buf_p++;
+				alpha = *buf_p++;
+				*pixbuf++ = red;
+				*pixbuf++ = green;
+				*pixbuf++ = blue;
+				*pixbuf++ = alpha;
+				break;
+			}
+		}
+	}
+
+	ri.FS_FreeFile( buffer.v );
+
+}

Added: trunk/code/rend2/tr_image_jpg.c
===================================================================
--- trunk/code/rend2/tr_image_jpg.c	                        (rev 0)
+++ trunk/code/rend2/tr_image_jpg.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,441 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+extern	refimport_t		ri;
+
+/*
+ * Include file for users of JPEG library.
+ * You will need to have included system headers that define at least
+ * the typedefs FILE and size_t before you can include jpeglib.h.
+ * (stdio.h is sufficient on ANSI-conforming systems.)
+ * You may also wish to include "jerror.h".
+ */
+
+#ifdef USE_INTERNAL_JPEG
+#  define JPEG_INTERNALS
+#endif
+
+#include <jpeglib.h>
+
+#ifndef USE_INTERNAL_JPEG
+#  if JPEG_LIB_VERSION < 80
+#    error Need system libjpeg >= 80
+#  endif
+#endif
+
+static void R_JPGErrorExit(j_common_ptr cinfo)
+{
+  char buffer[JMSG_LENGTH_MAX];
+  
+  (*cinfo->err->format_message) (cinfo, buffer);
+  
+  /* Let the memory manager delete any temp files before we die */
+  jpeg_destroy(cinfo);
+  
+  ri.Error(ERR_FATAL, "%s", buffer);
+}
+
+static void R_JPGOutputMessage(j_common_ptr cinfo)
+{
+  char buffer[JMSG_LENGTH_MAX];
+  
+  /* Create the message */
+  (*cinfo->err->format_message) (cinfo, buffer);
+  
+  /* Send it to stderr, adding a newline */
+  ri.Printf(PRINT_ALL, "%s\n", buffer);
+}
+
+void R_LoadJPG(const char *filename, unsigned char **pic, int *width, int *height)
+{
+  /* This struct contains the JPEG decompression parameters and pointers to
+   * working space (which is allocated as needed by the JPEG library).
+   */
+  struct jpeg_decompress_struct cinfo = {NULL};
+  /* We use our private extension JPEG error handler.
+   * Note that this struct must live as long as the main JPEG parameter
+   * struct, to avoid dangling-pointer problems.
+   */
+  /* This struct represents a JPEG error handler.  It is declared separately
+   * because applications often want to supply a specialized error handler
+   * (see the second half of this file for an example).  But here we just
+   * take the easy way out and use the standard error handler, which will
+   * print a message on stderr and call exit() if compression fails.
+   * Note that this struct must live as long as the main JPEG parameter
+   * struct, to avoid dangling-pointer problems.
+   */
+  struct jpeg_error_mgr jerr;
+  /* More stuff */
+  JSAMPARRAY buffer;		/* Output row buffer */
+  unsigned int row_stride;	/* physical row width in output buffer */
+  unsigned int pixelcount, memcount;
+  unsigned int sindex, dindex;
+  byte *out;
+  int len;
+	union {
+		byte *b;
+		void *v;
+	} fbuffer;
+  byte  *buf;
+
+  /* In this example we want to open the input file before doing anything else,
+   * so that the setjmp() error recovery below can assume the file is open.
+   * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+   * requires it in order to read binary files.
+   */
+
+  len = ri.FS_ReadFile ( ( char * ) filename, &fbuffer.v);
+  if (!fbuffer.b || len < 0) {
+	return;
+  }
+
+  /* Step 1: allocate and initialize JPEG decompression object */
+
+  /* We have to set up the error handler first, in case the initialization
+   * step fails.  (Unlikely, but it could happen if you are out of memory.)
+   * This routine fills in the contents of struct jerr, and returns jerr's
+   * address which we place into the link field in cinfo.
+   */
+  cinfo.err = jpeg_std_error(&jerr);
+  cinfo.err->error_exit = R_JPGErrorExit;
+  cinfo.err->output_message = R_JPGOutputMessage;
+
+  /* Now we can initialize the JPEG decompression object. */
+  jpeg_create_decompress(&cinfo);
+
+  /* Step 2: specify data source (eg, a file) */
+
+  jpeg_mem_src(&cinfo, fbuffer.b, len);
+
+  /* Step 3: read file parameters with jpeg_read_header() */
+
+  (void) jpeg_read_header(&cinfo, TRUE);
+  /* We can ignore the return value from jpeg_read_header since
+   *   (a) suspension is not possible with the stdio data source, and
+   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
+   * See libjpeg.doc for more info.
+   */
+
+  /* Step 4: set parameters for decompression */
+
+  /*
+   * Make sure it always converts images to RGB color space. This will
+   * automatically convert 8-bit greyscale images to RGB as well.
+   */
+  cinfo.out_color_space = JCS_RGB;
+
+  /* Step 5: Start decompressor */
+
+  (void) jpeg_start_decompress(&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  /* We may need to do some setup of our own at this point before reading
+   * the data.  After jpeg_start_decompress() we have the correct scaled
+   * output image dimensions available, as well as the output colormap
+   * if we asked for color quantization.
+   * In this example, we need to make an output work buffer of the right size.
+   */ 
+  /* JSAMPLEs per row in output buffer */
+
+  pixelcount = cinfo.output_width * cinfo.output_height;
+
+  if(!cinfo.output_width || !cinfo.output_height
+      || ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height
+      || pixelcount > 0x1FFFFFFF || cinfo.output_components != 3
+    )
+  {
+    // Free the memory to make sure we don't leak memory
+    ri.FS_FreeFile (fbuffer.v);
+    jpeg_destroy_decompress(&cinfo);
+  
+    ri.Error(ERR_DROP, "LoadJPG: %s has an invalid image format: %dx%d*4=%d, components: %d", filename,
+		    cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components);
+  }
+
+  memcount = pixelcount * 4;
+  row_stride = cinfo.output_width * cinfo.output_components;
+
+  out = ri.Malloc(memcount);
+
+  *width = cinfo.output_width;
+  *height = cinfo.output_height;
+
+  /* Step 6: while (scan lines remain to be read) */
+  /*           jpeg_read_scanlines(...); */
+
+  /* Here we use the library's state variable cinfo.output_scanline as the
+   * loop counter, so that we don't have to keep track ourselves.
+   */
+  while (cinfo.output_scanline < cinfo.output_height) {
+    /* jpeg_read_scanlines expects an array of pointers to scanlines.
+     * Here the array is only one element long, but you could ask for
+     * more than one scanline at a time if that's more convenient.
+     */
+	buf = ((out+(row_stride*cinfo.output_scanline)));
+	buffer = &buf;
+    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
+  }
+  
+  buf = out;
+
+  // Expand from RGB to RGBA
+  sindex = pixelcount * cinfo.output_components;
+  dindex = memcount;
+
+  do
+  {	
+    buf[--dindex] = 255;
+    buf[--dindex] = buf[--sindex];
+    buf[--dindex] = buf[--sindex];
+    buf[--dindex] = buf[--sindex];
+  } while(sindex);
+
+  *pic = out;
+
+  /* Step 7: Finish decompression */
+
+  jpeg_finish_decompress(&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  /* Step 8: Release JPEG decompression object */
+
+  /* This is an important step since it will release a good deal of memory. */
+  jpeg_destroy_decompress(&cinfo);
+
+  /* After finish_decompress, we can close the input file.
+   * Here we postpone it until after no more JPEG errors are possible,
+   * so as to simplify the setjmp error logic above.  (Actually, I don't
+   * think that jpeg_destroy can do an error exit, but why assume anything...)
+   */
+  ri.FS_FreeFile (fbuffer.v);
+
+  /* At this point you may want to check to see whether any corrupt-data
+   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
+   */
+
+  /* And we're done! */
+}
+
+
+/* Expanded data destination object for stdio output */
+
+typedef struct {
+  struct jpeg_destination_mgr pub; /* public fields */
+
+  byte* outfile;		/* target stream */
+  int	size;
+} my_destination_mgr;
+
+typedef my_destination_mgr * my_dest_ptr;
+
+
+/*
+ * Initialize destination --- called by jpeg_start_compress
+ * before any data is actually written.
+ */
+
+static void
+init_destination (j_compress_ptr cinfo)
+{
+  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
+
+  dest->pub.next_output_byte = dest->outfile;
+  dest->pub.free_in_buffer = dest->size;
+}
+
+
+/*
+ * Empty the output buffer --- called whenever buffer fills up.
+ *
+ * In typical applications, this should write the entire output buffer
+ * (ignoring the current state of next_output_byte & free_in_buffer),
+ * reset the pointer & count to the start of the buffer, and return TRUE
+ * indicating that the buffer has been dumped.
+ *
+ * In applications that need to be able to suspend compression due to output
+ * overrun, a FALSE return indicates that the buffer cannot be emptied now.
+ * In this situation, the compressor will return to its caller (possibly with
+ * an indication that it has not accepted all the supplied scanlines).  The
+ * application should resume compression after it has made more room in the
+ * output buffer.  Note that there are substantial restrictions on the use of
+ * suspension --- see the documentation.
+ *
+ * When suspending, the compressor will back up to a convenient restart point
+ * (typically the start of the current MCU). next_output_byte & free_in_buffer
+ * indicate where the restart point will be if the current call returns FALSE.
+ * Data beyond this point will be regenerated after resumption, so do not
+ * write it out when emptying the buffer externally.
+ */
+
+static boolean
+empty_output_buffer (j_compress_ptr cinfo)
+{
+  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
+  
+  jpeg_destroy_compress(cinfo);
+  
+  // Make crash fatal or we would probably leak memory.
+  ri.Error(ERR_FATAL, "Output buffer for encoded JPEG image has insufficient size of %d bytes",
+           dest->size);
+
+  return FALSE;
+}
+
+/*
+ * Terminate destination --- called by jpeg_finish_compress
+ * after all data has been written.  Usually needs to flush buffer.
+ *
+ * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
+ * application must deal with any cleanup that should happen even
+ * for error exit.
+ */
+
+static void term_destination(j_compress_ptr cinfo)
+{
+}
+
+
+/*
+ * Prepare for output to a stdio stream.
+ * The caller must have already opened the stream, and is responsible
+ * for closing it after finishing compression.
+ */
+
+static void
+jpegDest (j_compress_ptr cinfo, byte* outfile, int size)
+{
+  my_dest_ptr dest;
+
+  /* The destination object is made permanent so that multiple JPEG images
+   * can be written to the same file without re-executing jpeg_stdio_dest.
+   * This makes it dangerous to use this manager and a different destination
+   * manager serially with the same JPEG object, because their private object
+   * sizes may be different.  Caveat programmer.
+   */
+  if (cinfo->dest == NULL) {	/* first time for this JPEG object? */
+    cinfo->dest = (struct jpeg_destination_mgr *)
+      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+				  sizeof(my_destination_mgr));
+  }
+
+  dest = (my_dest_ptr) cinfo->dest;
+  dest->pub.init_destination = init_destination;
+  dest->pub.empty_output_buffer = empty_output_buffer;
+  dest->pub.term_destination = term_destination;
+  dest->outfile = outfile;
+  dest->size = size;
+}
+
+/*
+=================
+SaveJPGToBuffer
+
+Encodes JPEG from image in image_buffer and writes to buffer.
+Expects RGB input data
+=================
+*/
+size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,
+    int image_width, int image_height, byte *image_buffer, int padding)
+{
+  struct jpeg_compress_struct cinfo;
+  struct jpeg_error_mgr jerr;
+  JSAMPROW row_pointer[1];	/* pointer to JSAMPLE row[s] */
+  my_dest_ptr dest;
+  int row_stride;		/* physical row width in image buffer */
+  size_t outcount;
+
+  /* Step 1: allocate and initialize JPEG compression object */
+  cinfo.err = jpeg_std_error(&jerr);
+  cinfo.err->error_exit = R_JPGErrorExit;
+  cinfo.err->output_message = R_JPGOutputMessage;
+
+  /* Now we can initialize the JPEG compression object. */
+  jpeg_create_compress(&cinfo);
+
+  /* Step 2: specify data destination (eg, a file) */
+  /* Note: steps 2 and 3 can be done in either order. */
+  jpegDest(&cinfo, buffer, bufSize);
+
+  /* Step 3: set parameters for compression */
+  cinfo.image_width = image_width; 	/* image width and height, in pixels */
+  cinfo.image_height = image_height;
+  cinfo.input_components = 3;		/* # of color components per pixel */
+  cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
+
+  jpeg_set_defaults(&cinfo);
+  jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
+  /* If quality is set high, disable chroma subsampling */
+  if (quality >= 85) {
+    cinfo.comp_info[0].h_samp_factor = 1;
+    cinfo.comp_info[0].v_samp_factor = 1;
+  }
+
+  /* Step 4: Start compressor */
+  jpeg_start_compress(&cinfo, TRUE);
+
+  /* Step 5: while (scan lines remain to be written) */
+  /*           jpeg_write_scanlines(...); */
+  row_stride = image_width * cinfo.input_components + padding; /* JSAMPLEs per row in image_buffer */
+  
+  while (cinfo.next_scanline < cinfo.image_height) {
+    /* jpeg_write_scanlines expects an array of pointers to scanlines.
+     * Here the array is only one element long, but you could pass
+     * more than one scanline at a time if that's more convenient.
+     */
+    row_pointer[0] = &image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
+    (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+  }
+
+  /* Step 6: Finish compression */
+  jpeg_finish_compress(&cinfo);
+  
+  dest = (my_dest_ptr) cinfo.dest;
+  outcount = dest->size - dest->pub.free_in_buffer;
+ 
+  /* Step 7: release JPEG compression object */
+  jpeg_destroy_compress(&cinfo);
+
+  /* And we're done! */
+  return outcount;
+}
+
+void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, byte *image_buffer, int padding)
+{
+  byte *out;
+  size_t bufSize;
+
+  bufSize = image_width * image_height * 3;
+  out = ri.Hunk_AllocateTempMemory(bufSize);
+
+  bufSize = RE_SaveJPGToBuffer(out, bufSize, quality, image_width, image_height, image_buffer, padding);
+  ri.FS_WriteFile(filename, out, bufSize);
+
+  ri.Hunk_FreeTempMemory(out);
+}

Added: trunk/code/rend2/tr_image_pcx.c
===================================================================
--- trunk/code/rend2/tr_image_pcx.c	                        (rev 0)
+++ trunk/code/rend2/tr_image_pcx.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,179 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+              2008 Ludwig Nussel
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+extern	refimport_t		ri;
+
+/*
+========================================================================
+
+PCX files are used for 8 bit images
+
+========================================================================
+*/
+
+typedef struct {
+	char	manufacturer;
+	char	version;
+	char	encoding;
+	char	bits_per_pixel;
+	unsigned short	xmin,ymin,xmax,ymax;
+	unsigned short	hres,vres;
+	unsigned char	palette[48];
+	char	reserved;
+	char	color_planes;
+	unsigned short	bytes_per_line;
+	unsigned short	palette_type;
+	unsigned short	hscreensize, vscreensize;
+	char	filler[54];
+	unsigned char	data[];
+} pcx_t;
+
+void R_LoadPCX ( const char *filename, byte **pic, int *width, int *height)
+{
+	union {
+		byte *b;
+		void *v;
+	} raw;
+	byte	*end;
+	pcx_t	*pcx;
+	int		len;
+	unsigned char	dataByte = 0, runLength = 0;
+	byte	*out, *pix;
+	unsigned short w, h;
+	byte	*pic8;
+	byte	*palette;
+	int	i;
+	unsigned size = 0;
+
+	if (width)
+		*width = 0;
+	if (height)
+		*height = 0;
+	*pic = NULL;
+
+	//
+	// load the file
+	//
+	len = ri.FS_ReadFile( ( char * ) filename, &raw.v);
+	if (!raw.b || len < 0) {
+		return;
+	}
+
+	if((unsigned)len < sizeof(pcx_t))
+	{
+		ri.Printf (PRINT_ALL, "PCX truncated: %s\n", filename);
+		ri.FS_FreeFile (raw.v);
+		return;
+	}
+
+	//
+	// parse the PCX file
+	//
+	pcx = (pcx_t *)raw.b;
+	end = raw.b+len;
+
+	w = LittleShort(pcx->xmax)+1;
+	h = LittleShort(pcx->ymax)+1;
+	size = w*h;
+
+	if (pcx->manufacturer != 0x0a
+		|| pcx->version != 5
+		|| pcx->encoding != 1
+		|| pcx->color_planes != 1
+		|| pcx->bits_per_pixel != 8
+		|| w >= 1024
+		|| h >= 1024)
+	{
+		ri.Printf (PRINT_ALL, "Bad or unsupported pcx file %s (%dx%d@%d)\n", filename, w, h, pcx->bits_per_pixel);
+		return;
+	}
+
+	pix = pic8 = ri.Malloc ( size );
+
+	raw.b = pcx->data;
+	// FIXME: should use bytes_per_line but original q3 didn't do that either
+	while(pix < pic8+size)
+	{
+		if(runLength > 0) {
+			*pix++ = dataByte;
+			--runLength;
+			continue;
+		}
+
+		if(raw.b+1 > end)
+			break;
+		dataByte = *raw.b++;
+
+		if((dataByte & 0xC0) == 0xC0)
+		{
+			if(raw.b+1 > end)
+				break;
+			runLength = dataByte & 0x3F;
+			dataByte = *raw.b++;
+		}
+		else
+			runLength = 1;
+	}
+
+	if(pix < pic8+size)
+	{
+		ri.Printf (PRINT_ALL, "PCX file truncated: %s\n", filename);
+		ri.FS_FreeFile (pcx);
+		ri.Free (pic8);
+	}
+
+	if (raw.b-(byte*)pcx >= end - (byte*)769 || end[-769] != 0x0c)
+	{
+		ri.Printf (PRINT_ALL, "PCX missing palette: %s\n", filename);
+		ri.FS_FreeFile (pcx);
+		ri.Free (pic8);
+		return;
+	}
+
+	palette = end-768;
+
+	pix = out = ri.Malloc(4 * size );
+	for (i = 0 ; i < size ; i++)
+	{
+		unsigned char p = pic8[i];
+		pix[0] = palette[p*3];
+		pix[1] = palette[p*3 + 1];
+		pix[2] = palette[p*3 + 2];
+		pix[3] = 255;
+		pix += 4;
+	}
+
+	if (width)
+		*width = w;
+	if (height)
+		*height = h;
+
+	*pic = out;
+
+	ri.FS_FreeFile (pcx);
+	ri.Free (pic8);
+}

Added: trunk/code/rend2/tr_image_png.c
===================================================================
--- trunk/code/rend2/tr_image_png.c	                        (rev 0)
+++ trunk/code/rend2/tr_image_png.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,2490 @@
+/*
+===========================================================================
+ioquake3 png decoder
+Copyright (C) 2007,2008 Joerg Dietrich
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+extern	refimport_t		ri;
+
+#include "../qcommon/puff.h"
+
+// we could limit the png size to a lower value here
+#ifndef INT_MAX
+#define INT_MAX 0x1fffffff
+#endif
+
+/*
+=================
+PNG LOADING
+=================
+*/
+
+/*
+ *  Quake 3 image format : RGBA
+ */
+
+#define Q3IMAGE_BYTESPERPIXEL (4)
+
+/*
+ *  PNG specifications
+ */
+
+/*
+ *  The first 8 Bytes of every PNG-File are a fixed signature
+ *  to identify the file as a PNG.
+ */
+
+#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA"
+#define PNG_Signature_Size (8)
+
+/*
+ *  After the signature diverse chunks follow.
+ *  A chunk consists of a header and if Length
+ *  is bigger than 0 a body and a CRC of the body follow.
+ */
+
+struct PNG_ChunkHeader
+{
+	uint32_t Length;
+	uint32_t Type;
+};
+
+#define PNG_ChunkHeader_Size (8)
+
+typedef uint32_t PNG_ChunkCRC;
+
+#define PNG_ChunkCRC_Size (4)
+
+/*
+ *  We use the following ChunkTypes.
+ *  All others are ignored.
+ */
+
+#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d)))
+
+#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R')
+#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E')
+#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T')
+#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D')
+#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S')
+
+/*
+ *  Per specification the first chunk after the signature SHALL be IHDR.
+ */
+
+struct PNG_Chunk_IHDR
+{
+	uint32_t Width;
+	uint32_t Height;
+	uint8_t  BitDepth;
+	uint8_t  ColourType;
+	uint8_t  CompressionMethod;
+	uint8_t  FilterMethod;
+	uint8_t  InterlaceMethod;
+};
+
+#define PNG_Chunk_IHDR_Size (13)
+
+/*
+ *  ColourTypes
+ */
+
+#define PNG_ColourType_Grey      (0)
+#define PNG_ColourType_True      (2)
+#define PNG_ColourType_Indexed   (3)
+#define PNG_ColourType_GreyAlpha (4)
+#define PNG_ColourType_TrueAlpha (6)
+
+/*
+ *  number of colour components
+ *
+ *  Grey      : 1 grey
+ *  True      : 1 R, 1 G, 1 B
+ *  Indexed   : 1 index
+ *  GreyAlpha : 1 grey, 1 alpha
+ *  TrueAlpha : 1 R, 1 G, 1 B, 1 alpha
+ */
+
+#define PNG_NumColourComponents_Grey      (1)
+#define PNG_NumColourComponents_True      (3)
+#define PNG_NumColourComponents_Indexed   (1)
+#define PNG_NumColourComponents_GreyAlpha (2)
+#define PNG_NumColourComponents_TrueAlpha (4)
+
+/*
+ *  For the different ColourTypes
+ *  different BitDepths are specified.
+ */
+
+#define PNG_BitDepth_1  ( 1)
+#define PNG_BitDepth_2  ( 2)
+#define PNG_BitDepth_4  ( 4)
+#define PNG_BitDepth_8  ( 8)
+#define PNG_BitDepth_16 (16)
+
+/*
+ *  Only one valid CompressionMethod is standardized.
+ */
+
+#define PNG_CompressionMethod_0 (0)
+
+/*
+ *  Only one valid FilterMethod is currently standardized.
+ */
+
+#define PNG_FilterMethod_0 (0)
+
+/*
+ *  This FilterMethod defines 5 FilterTypes
+ */
+
+#define PNG_FilterType_None    (0)
+#define PNG_FilterType_Sub     (1)
+#define PNG_FilterType_Up      (2)
+#define PNG_FilterType_Average (3)
+#define PNG_FilterType_Paeth   (4)
+
+/*
+ *  Two InterlaceMethods are standardized :
+ *  0 - NonInterlaced
+ *  1 - Interlaced
+ */
+
+#define PNG_InterlaceMethod_NonInterlaced (0)
+#define PNG_InterlaceMethod_Interlaced    (1)
+
+/*
+ *  The Adam7 interlace method uses 7 passes.
+ */
+
+#define PNG_Adam7_NumPasses (7)
+
+/*
+ *  The compressed data starts with a header ...
+ */
+
+struct PNG_ZlibHeader
+{
+	uint8_t CompressionMethod;
+	uint8_t Flags;
+};
+
+#define PNG_ZlibHeader_Size (2)
+
+/*
+ *  ... and is followed by a check value
+ */
+
+#define PNG_ZlibCheckValue_Size (4)
+
+/*
+ *  Some support functions for buffered files follow.
+ */
+
+/*
+ *  buffered file representation
+ */
+
+struct BufferedFile
+{
+	byte *Buffer;
+	int   Length;
+	byte *Ptr;
+	int   BytesLeft;
+};
+
+/*
+ *  Read a file into a buffer.
+ */
+
+static struct BufferedFile *ReadBufferedFile(const char *name)
+{
+	struct BufferedFile *BF;
+	union {
+		byte *b;
+		void *v;
+	} buffer;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!name)
+	{
+		return(NULL);
+	}
+
+	/*
+	 *  Allocate control struct.
+	 */
+
+	BF = ri.Malloc(sizeof(struct BufferedFile));
+	if(!BF)
+	{
+		return(NULL);
+	}
+
+	/*
+	 *  Initialize the structs components.
+	 */
+
+	BF->Length    = 0;
+	BF->Buffer    = NULL;
+	BF->Ptr       = NULL;
+	BF->BytesLeft = 0;
+
+	/*
+	 *  Read the file.
+	 */
+
+	BF->Length = ri.FS_ReadFile((char *) name, &buffer.v);
+	BF->Buffer = buffer.b;
+
+	/*
+	 *  Did we get it? Is it big enough?
+	 */
+
+	if(!(BF->Buffer && (BF->Length > 0)))
+	{
+		ri.Free(BF);
+
+		return(NULL);
+	}
+
+	/*
+	 *  Set the pointers and counters.
+	 */
+
+	BF->Ptr       = BF->Buffer;
+	BF->BytesLeft = BF->Length;
+
+	return(BF);
+}
+
+/*
+ *  Close a buffered file.
+ */
+
+static void CloseBufferedFile(struct BufferedFile *BF)
+{
+	if(BF)
+	{
+		if(BF->Buffer)
+		{
+			ri.FS_FreeFile(BF->Buffer);
+		}
+
+		ri.Free(BF);
+	}
+}
+
+/*
+ *  Get a pointer to the requested bytes.
+ */
+
+static void *BufferedFileRead(struct BufferedFile *BF, unsigned Length)
+{
+	void *RetVal;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(BF && Length))
+	{
+		return(NULL);
+	}
+
+	/*
+	 *  not enough bytes left
+	 */
+
+	if(Length > BF->BytesLeft)
+	{
+		return(NULL);
+	}
+
+	/*
+	 *  the pointer to the requested data
+	 */
+
+	RetVal = BF->Ptr;
+
+	/*
+	 *  Raise the pointer and counter.
+	 */
+
+	BF->Ptr       += Length;
+	BF->BytesLeft -= Length;
+
+	return(RetVal);
+}
+
+/*
+ *  Rewind the buffer.
+ */
+
+static qboolean BufferedFileRewind(struct BufferedFile *BF, unsigned Offset)
+{
+	unsigned BytesRead; 
+
+	/*
+	 *  input verification
+	 */
+
+	if(!BF)
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  special trick to rewind to the beginning of the buffer
+	 */
+
+	if(Offset == (unsigned)-1)
+	{
+		BF->Ptr       = BF->Buffer;
+		BF->BytesLeft = BF->Length;
+
+		return(qtrue);
+	}
+
+	/*
+	 *  How many bytes do we have already read?
+	 */
+
+	BytesRead = BF->Ptr - BF->Buffer;
+
+	/*
+	 *  We can only rewind to the beginning of the BufferedFile.
+	 */
+
+	if(Offset > BytesRead)
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  lower the pointer and counter.
+	 */
+
+	BF->Ptr       -= Offset;
+	BF->BytesLeft += Offset;
+
+	return(qtrue);
+}
+
+/*
+ *  Skip some bytes.
+ */
+
+static qboolean BufferedFileSkip(struct BufferedFile *BF, unsigned Offset)
+{
+	/*
+	 *  input verification
+	 */
+
+	if(!BF)
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  We can only skip to the end of the BufferedFile.
+	 */
+
+	if(Offset > BF->BytesLeft)
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  lower the pointer and counter.
+	 */
+
+	BF->Ptr       += Offset;
+	BF->BytesLeft -= Offset;
+
+	return(qtrue);
+}
+
+/*
+ *  Find a chunk
+ */
+
+static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType)
+{
+	struct PNG_ChunkHeader *CH;
+
+	uint32_t Length;
+	uint32_t Type;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!BF)
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  cycle trough the chunks
+	 */
+
+	while(qtrue)
+	{
+		/*
+		 *  Read the chunk-header.
+		 */
+
+		CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+		if(!CH)
+		{
+			return(qfalse);
+		}
+
+		/*
+		 *  Do not swap the original types
+		 *  they might be needed later.
+		 */
+
+		Length = BigLong(CH->Length);
+		Type   = BigLong(CH->Type);
+
+		/*
+		 *  We found it!
+		 */
+
+		if(Type == ChunkType)
+		{
+			/*
+			 *  Rewind to the start of the chunk.
+			 */
+
+			BufferedFileRewind(BF, PNG_ChunkHeader_Size);
+
+			break;
+		}
+		else
+		{
+			/*
+			 *  Skip the rest of the chunk.
+			 */
+
+			if(Length)
+			{
+				if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
+				{
+					return(qfalse);
+				}  
+			}
+		}
+	}
+
+	return(qtrue);
+}
+
+/*
+ *  Decompress all IDATs
+ */
+
+static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer)
+{
+	uint8_t  *DecompressedData;
+	uint32_t  DecompressedDataLength;
+
+	uint8_t  *CompressedData;
+	uint8_t  *CompressedDataPtr;
+	uint32_t  CompressedDataLength;
+
+	struct PNG_ChunkHeader *CH;
+
+	uint32_t Length;
+	uint32_t Type;
+
+	int BytesToRewind;
+
+	int32_t   puffResult;
+	uint8_t  *puffDest;
+	uint32_t  puffDestLen;
+	uint8_t  *puffSrc;
+	uint32_t  puffSrcLen;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(BF && Buffer))
+	{
+		return(-1);
+	}
+
+	/*
+	 *  some zeroing
+	 */
+
+	DecompressedData = NULL;
+	DecompressedDataLength = 0;
+	*Buffer = DecompressedData;
+
+	CompressedData = NULL;
+	CompressedDataLength = 0;
+
+	BytesToRewind = 0;
+
+	/*
+	 *  Find the first IDAT chunk.
+	 */
+
+	if(!FindChunk(BF, PNG_ChunkType_IDAT))
+	{
+		return(-1);
+	}
+
+	/*
+	 *  Count the size of the uncompressed data
+	 */
+
+	while(qtrue)
+	{
+		/*
+		 *  Read chunk header
+		 */
+
+		CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+		if(!CH)
+		{
+			/*
+			 *  Rewind to the start of this adventure
+			 *  and return unsuccessfull
+			 */
+
+			BufferedFileRewind(BF, BytesToRewind);
+
+			return(-1);
+		}
+
+		/*
+		 *  Length and Type of chunk
+		 */
+
+		Length = BigLong(CH->Length);
+		Type   = BigLong(CH->Type);
+
+		/*
+		 *  We have reached the end of the IDAT chunks
+		 */
+
+		if(!(Type == PNG_ChunkType_IDAT))
+		{
+			BufferedFileRewind(BF, PNG_ChunkHeader_Size); 
+
+			break;
+		}
+
+		/*
+		 *  Add chunk header to count.
+		 */
+
+		BytesToRewind += PNG_ChunkHeader_Size;
+
+		/*
+		 *  Skip to next chunk
+		 */
+
+		if(Length)
+		{
+			if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
+			{
+				BufferedFileRewind(BF, BytesToRewind);
+
+				return(-1);
+			}
+
+			BytesToRewind += Length + PNG_ChunkCRC_Size;
+			CompressedDataLength += Length;
+		} 
+	}
+
+	BufferedFileRewind(BF, BytesToRewind);
+
+	CompressedData = ri.Malloc(CompressedDataLength);
+	if(!CompressedData)
+	{
+		return(-1);
+	}
+
+	CompressedDataPtr = CompressedData;
+
+	/*
+	 *  Collect the compressed Data
+	 */
+
+	while(qtrue)
+	{
+		/*
+		 *  Read chunk header
+		 */
+
+		CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+		if(!CH)
+		{
+			ri.Free(CompressedData); 
+
+			return(-1);
+		}
+
+		/*
+		 *  Length and Type of chunk
+		 */
+
+		Length = BigLong(CH->Length);
+		Type   = BigLong(CH->Type);
+
+		/*
+		 *  We have reached the end of the IDAT chunks
+		 */
+
+		if(!(Type == PNG_ChunkType_IDAT))
+		{
+			BufferedFileRewind(BF, PNG_ChunkHeader_Size); 
+
+			break;
+		}
+
+		/*
+		 *  Copy the Data
+		 */
+
+		if(Length)
+		{
+			uint8_t *OrigCompressedData;
+
+			OrigCompressedData = BufferedFileRead(BF, Length);
+			if(!OrigCompressedData)
+			{
+				ri.Free(CompressedData); 
+
+				return(-1);
+			}
+
+			if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size))
+			{
+				ri.Free(CompressedData); 
+
+				return(-1);
+			}
+
+			memcpy(CompressedDataPtr, OrigCompressedData, Length);
+			CompressedDataPtr += Length;
+		} 
+	}
+
+	/*
+	 *  Let puff() calculate the decompressed data length.
+	 */
+
+	puffDest    = NULL;
+	puffDestLen = 0;
+
+	/*
+	 *  The zlib header and checkvalue don't belong to the compressed data.
+	 */
+
+	puffSrc    = CompressedData + PNG_ZlibHeader_Size;
+	puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
+
+	/*
+	 *  first puff() to calculate the size of the uncompressed data
+	 */
+
+	puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
+	if(!((puffResult == 0) && (puffDestLen > 0)))
+	{
+		ri.Free(CompressedData);
+
+		return(-1);
+	}
+
+	/*
+	 *  Allocate the buffer for the uncompressed data.
+	 */
+
+	DecompressedData = ri.Malloc(puffDestLen);
+	if(!DecompressedData)
+	{
+		ri.Free(CompressedData);
+
+		return(-1);
+	}
+
+	/*
+	 *  Set the input again in case something was changed by the last puff() .
+	 */
+
+	puffDest   = DecompressedData;
+	puffSrc    = CompressedData + PNG_ZlibHeader_Size;
+	puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
+
+	/*
+	 *  decompression puff()
+	 */
+
+	puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
+
+	/*
+	 *  The compressed data is not needed anymore.
+	 */
+
+	ri.Free(CompressedData);
+
+	/*
+	 *  Check if the last puff() was successfull.
+	 */
+
+	if(!((puffResult == 0) && (puffDestLen > 0)))
+	{
+		ri.Free(DecompressedData);
+
+		return(-1);
+	}
+
+	/*
+	 *  Set the output of this function.
+	 */
+
+	DecompressedDataLength = puffDestLen;
+	*Buffer = DecompressedData;
+
+	return(DecompressedDataLength);
+}
+
+/*
+ *  the Paeth predictor
+ */
+
+static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c)
+{
+	/*
+	 *  a == Left
+	 *  b == Up
+	 *  c == UpLeft
+	 */
+
+	uint8_t Pr;
+	int p;
+	int pa, pb, pc;
+
+	p  = ((int) a) + ((int) b) - ((int) c);
+	pa = abs(p - ((int) a));
+	pb = abs(p - ((int) b));
+	pc = abs(p - ((int) c));
+
+	if((pa <= pb) && (pa <= pc))
+	{
+		Pr = a;
+	}
+	else if(pb <= pc)
+	{
+		Pr = b;
+	}
+	else
+	{
+		Pr = c;
+	}
+
+	return(Pr);
+
+}
+
+/*
+ *  Reverse the filters.
+ */
+
+static qboolean UnfilterImage(uint8_t  *DecompressedData, 
+		uint32_t  ImageHeight,
+		uint32_t  BytesPerScanline, 
+		uint32_t  BytesPerPixel)
+{
+	uint8_t   *DecompPtr;
+	uint8_t   FilterType;
+	uint8_t  *PixelLeft, *PixelUp, *PixelUpLeft;
+	uint32_t  w, h, p;
+
+	/*
+	 *  some zeros for the filters
+	 */
+
+	uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(DecompressedData && BytesPerPixel))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  ImageHeight and BytesPerScanline can be zero in small interlaced images.
+	 */
+
+	if((!ImageHeight) || (!BytesPerScanline))
+	{
+		return(qtrue);
+	}
+
+	/*
+	 *  Set the pointer to the start of the decompressed Data.
+	 */
+
+	DecompPtr = DecompressedData;
+
+	/*
+	 *  Un-filtering is done in place.
+	 */
+
+	/*
+	 *  Go trough all scanlines.
+	 */
+
+	for(h = 0; h < ImageHeight; h++)
+	{
+		/*
+		 *  Every scanline starts with a FilterType byte.
+		 */
+
+		FilterType = *DecompPtr;
+		DecompPtr++;
+
+		/*
+		 *  Left pixel of the first byte in a scanline is zero.
+		 */
+
+		PixelLeft = Zeros;
+
+		/*
+		 *  Set PixelUp to previous line only if we are on the second line or above.
+		 *
+		 *  Plus one byte for the FilterType
+		 */
+
+		if(h > 0)
+		{
+			PixelUp = DecompPtr - (BytesPerScanline + 1);
+		}
+		else
+		{
+			PixelUp = Zeros;
+		}
+
+		/*
+		 * The pixel left to the first pixel of the previous scanline is zero too.
+		 */
+
+		PixelUpLeft = Zeros;
+
+		/*
+		 *  Cycle trough all pixels of the scanline.
+		 */
+
+		for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
+		{
+			/*
+			 *  Cycle trough the bytes of the pixel.
+			 */
+
+			for(p = 0; p < BytesPerPixel; p++)
+			{
+				switch(FilterType)
+				{ 
+					case PNG_FilterType_None :
+					{
+						/*
+						 *  The byte is unfiltered.
+						 */
+
+						break;
+					}
+
+					case PNG_FilterType_Sub :
+					{
+						DecompPtr[p] += PixelLeft[p];
+
+						break;
+					}
+
+					case PNG_FilterType_Up :
+					{
+						DecompPtr[p] += PixelUp[p];
+
+						break;
+					}
+
+					case PNG_FilterType_Average :
+					{
+						DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2));
+
+						break;
+					}
+
+					case PNG_FilterType_Paeth :
+					{
+						DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]);
+
+						break;
+					}
+
+					default :
+					{
+						return(qfalse);
+					}
+				}
+			}
+
+			PixelLeft = DecompPtr;
+
+			/*
+			 *  We only have an upleft pixel if we are on the second line or above.
+			 */
+
+			if(h > 0)
+			{
+				PixelUpLeft = DecompPtr - (BytesPerScanline + 1);
+			}
+
+			/*
+			 *  Skip to the next pixel.
+			 */
+
+			DecompPtr += BytesPerPixel;
+
+			/*
+			 *  We only have a previous line if we are on the second line and above.
+			 */
+
+			if(h > 0)
+			{
+				PixelUp = DecompPtr - (BytesPerScanline + 1);
+			}
+		}
+	}
+
+	return(qtrue);
+}
+
+/*
+ *  Convert a raw input pixel to Quake 3 RGA format.
+ */
+
+static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR,
+		byte                  *OutPtr,
+		uint8_t               *DecompPtr,
+		qboolean               HasTransparentColour,
+		uint8_t               *TransparentColour,
+		uint8_t               *OutPal)
+{
+	/*
+	 *  input verification
+	 */
+
+	if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal))
+	{
+		return(qfalse);
+	}
+
+	switch(IHDR->ColourType)
+	{
+		case PNG_ColourType_Grey :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_1 :
+				case PNG_BitDepth_2 :
+				case PNG_BitDepth_4 :
+				{
+					uint8_t Step;
+					uint8_t GreyValue;
+
+					Step = 0xFF / ((1 << IHDR->BitDepth) - 1);
+
+					GreyValue = DecompPtr[0] * Step;
+
+					OutPtr[0] = GreyValue;
+					OutPtr[1] = GreyValue;
+					OutPtr[2] = GreyValue;
+					OutPtr[3] = 0xFF;
+
+					/*
+					 *  Grey supports full transparency for one specified colour
+					 */
+
+					if(HasTransparentColour)
+					{
+						if(TransparentColour[1] == DecompPtr[0])
+						{
+							OutPtr[3] = 0x00;
+						}
+					}
+
+
+					break;
+				}
+
+				case PNG_BitDepth_8 :
+				case PNG_BitDepth_16 :
+				{
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[0];
+					OutPtr[2] = DecompPtr[0];
+					OutPtr[3] = 0xFF;
+
+					/*
+					 *  Grey supports full transparency for one specified colour
+					 */
+
+					if(HasTransparentColour)
+					{
+						if(IHDR->BitDepth == PNG_BitDepth_8)
+						{
+							if(TransparentColour[1] == DecompPtr[0])
+							{
+								OutPtr[3] = 0x00;
+							}
+						}
+						else
+						{
+							if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]))
+							{
+								OutPtr[3] = 0x00;
+							}
+						}
+					}
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_True :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				{
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[1];
+					OutPtr[2] = DecompPtr[2];
+					OutPtr[3] = 0xFF;
+
+					/*
+					 *  True supports full transparency for one specified colour
+					 */
+
+					if(HasTransparentColour)
+					{
+						if((TransparentColour[1] == DecompPtr[0]) &&
+								(TransparentColour[3] == DecompPtr[1]) &&
+								(TransparentColour[5] == DecompPtr[2]))
+						{
+							OutPtr[3] = 0x00;
+						}
+					}
+
+					break;
+				}
+
+				case PNG_BitDepth_16 :
+				{
+					/*
+					 *  We use only the upper byte.
+					 */
+
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[2];
+					OutPtr[2] = DecompPtr[4];
+					OutPtr[3] = 0xFF;
+
+					/*
+					 *  True supports full transparency for one specified colour
+					 */
+
+					if(HasTransparentColour)
+					{
+						if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) &&
+								(TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) &&
+								(TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5]))
+						{
+							OutPtr[3] = 0x00;
+						}
+					}
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_Indexed :
+		{
+			OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0];
+			OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1];
+			OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2];
+			OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3];
+
+			break;
+		}
+
+		case PNG_ColourType_GreyAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				{
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[0];
+					OutPtr[2] = DecompPtr[0];
+					OutPtr[3] = DecompPtr[1];
+
+					break;
+				}
+
+				case PNG_BitDepth_16 :
+				{
+					/*
+					 *  We use only the upper byte.
+					 */
+
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[0];
+					OutPtr[2] = DecompPtr[0];
+					OutPtr[3] = DecompPtr[2];
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_TrueAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				{
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[1];
+					OutPtr[2] = DecompPtr[2];
+					OutPtr[3] = DecompPtr[3];
+
+					break;
+				}
+
+				case PNG_BitDepth_16 :
+				{
+					/*
+					 *  We use only the upper byte.
+					 */
+
+					OutPtr[0] = DecompPtr[0];
+					OutPtr[1] = DecompPtr[2];
+					OutPtr[2] = DecompPtr[4];
+					OutPtr[3] = DecompPtr[6];
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		default :
+		{
+			return(qfalse);
+		}
+	}
+
+	return(qtrue);
+}
+
+
+/*
+ *  Decode a non-interlaced image.
+ */
+
+static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR,
+		byte                  *OutBuffer, 
+		uint8_t               *DecompressedData,
+		uint32_t               DecompressedDataLength,
+		qboolean               HasTransparentColour,
+		uint8_t               *TransparentColour,
+		uint8_t               *OutPal)
+{
+	uint32_t IHDR_Width;
+	uint32_t IHDR_Height;
+	uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte;
+	uint32_t  w, h, p;
+	byte *OutPtr;
+	uint8_t *DecompPtr;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  byte swapping
+	 */
+
+	IHDR_Width  = BigLong(IHDR->Width);
+	IHDR_Height = BigLong(IHDR->Height);
+
+	/*
+	 *  information for un-filtering
+	 */
+
+	switch(IHDR->ColourType)
+	{
+		case PNG_ColourType_Grey :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_1 :
+				case PNG_BitDepth_2 :
+				case PNG_BitDepth_4 :
+				{
+					BytesPerPixel    = 1;
+					PixelsPerByte    = 8 / IHDR->BitDepth;
+
+					break;
+				}
+
+				case PNG_BitDepth_8  :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_True :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8  :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_Indexed :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_1 :
+				case PNG_BitDepth_2 :
+				case PNG_BitDepth_4 :
+				{
+					BytesPerPixel    = 1;
+					PixelsPerByte    = 8 / IHDR->BitDepth;
+
+					break;
+				}
+
+				case PNG_BitDepth_8 :
+				{
+					BytesPerPixel    = PNG_NumColourComponents_Indexed;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_GreyAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_TrueAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		default :
+		{
+			return(qfalse);
+		}
+	}
+
+	/*
+	 *  Calculate the size of one scanline
+	 */
+
+	BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
+
+	/*
+	 *  Check if we have enough data for the whole image.
+	 */
+
+	if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height)))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  Unfilter the image.
+	 */
+
+	if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  Set the working pointers to the beginning of the buffers.
+	 */
+
+	OutPtr = OutBuffer;
+	DecompPtr = DecompressedData;
+
+	/*
+	 *  Create the output image.
+	 */
+
+	for(h = 0; h < IHDR_Height; h++)
+	{
+		/*
+		 *  Count the pixels on the scanline for those multipixel bytes
+		 */
+
+		uint32_t CurrPixel;
+
+		/*
+		 *  skip FilterType
+		 */
+
+		DecompPtr++;
+
+		/*
+		 *  Reset the pixel count.
+		 */
+
+		CurrPixel = 0;
+
+		for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
+		{
+			if(PixelsPerByte > 1)
+			{
+				uint8_t  Mask;
+				uint32_t Shift;
+				uint8_t  SinglePixel;
+
+				for(p = 0; p < PixelsPerByte; p++)
+				{
+					if(CurrPixel < IHDR_Width)
+					{
+						Mask  = (1 << IHDR->BitDepth) - 1;
+						Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
+
+						SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
+
+						if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
+						{
+							return(qfalse);
+						}
+
+						OutPtr += Q3IMAGE_BYTESPERPIXEL;
+						CurrPixel++;
+					}
+				}
+
+			}
+			else
+			{
+				if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
+				{
+					return(qfalse);
+				}
+
+
+				OutPtr += Q3IMAGE_BYTESPERPIXEL;
+			}
+
+			DecompPtr += BytesPerPixel;
+		}
+	}
+
+	return(qtrue);
+}
+
+/*
+ *  Decode an interlaced image.
+ */
+
+static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR,
+		byte                  *OutBuffer, 
+		uint8_t               *DecompressedData,
+		uint32_t               DecompressedDataLength,
+		qboolean               HasTransparentColour,
+		uint8_t               *TransparentColour,
+		uint8_t               *OutPal)
+{
+	uint32_t IHDR_Width;
+	uint32_t IHDR_Height;
+	uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte;
+	uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses];
+	uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses];
+	uint32_t w, h, p, a;
+	byte *OutPtr;
+	uint8_t *DecompPtr;
+	uint32_t TargetLength;
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  byte swapping
+	 */
+
+	IHDR_Width  = BigLong(IHDR->Width);
+	IHDR_Height = BigLong(IHDR->Height);
+
+	/*
+	 *  Skip and Offset for the passes.
+	 */
+
+	WSkip[0]   = 8;
+	WOffset[0] = 0;
+	HSkip[0]   = 8;
+	HOffset[0] = 0;
+
+	WSkip[1]   = 8;
+	WOffset[1] = 4;
+	HSkip[1]   = 8;
+	HOffset[1] = 0;
+
+	WSkip[2]   = 4;
+	WOffset[2] = 0;
+	HSkip[2]   = 8;
+	HOffset[2] = 4;
+
+	WSkip[3]   = 4;
+	WOffset[3] = 2;
+	HSkip[3]   = 4;
+	HOffset[3] = 0;
+
+	WSkip[4]   = 2;
+	WOffset[4] = 0;
+	HSkip[4]   = 4;
+	HOffset[4] = 2;
+
+	WSkip[5]   = 2;
+	WOffset[5] = 1;
+	HSkip[5]   = 2;
+	HOffset[5] = 0;
+
+	WSkip[6]   = 1;
+	WOffset[6] = 0;
+	HSkip[6]   = 2;
+	HOffset[6] = 1;
+
+	/*
+	 *  Calculate the sizes of the passes.
+	 */
+
+	PassWidth[0]  = (IHDR_Width  + 7) / 8;
+	PassHeight[0] = (IHDR_Height + 7) / 8;
+
+	PassWidth[1]  = (IHDR_Width  + 3) / 8;
+	PassHeight[1] = (IHDR_Height + 7) / 8;
+
+	PassWidth[2]  = (IHDR_Width  + 3) / 4;
+	PassHeight[2] = (IHDR_Height + 3) / 8;
+
+	PassWidth[3]  = (IHDR_Width  + 1) / 4;
+	PassHeight[3] = (IHDR_Height + 3) / 4;
+
+	PassWidth[4]  = (IHDR_Width  + 1) / 2;
+	PassHeight[4] = (IHDR_Height + 1) / 4;
+
+	PassWidth[5]  = (IHDR_Width  + 0) / 2;
+	PassHeight[5] = (IHDR_Height + 1) / 2;
+
+	PassWidth[6]  = (IHDR_Width  + 0) / 1;
+	PassHeight[6] = (IHDR_Height + 0) / 2;
+
+	/*
+	 *  information for un-filtering
+	 */
+
+	switch(IHDR->ColourType)
+	{
+		case PNG_ColourType_Grey :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_1 :
+				case PNG_BitDepth_2 :
+				case PNG_BitDepth_4 :
+				{
+					BytesPerPixel    = 1;
+					PixelsPerByte    = 8 / IHDR->BitDepth;
+
+					break;
+				}
+
+				case PNG_BitDepth_8  :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_True :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8  :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_Indexed :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_1 :
+				case PNG_BitDepth_2 :
+				case PNG_BitDepth_4 :
+				{
+					BytesPerPixel    = 1;
+					PixelsPerByte    = 8 / IHDR->BitDepth;
+
+					break;
+				}
+
+				case PNG_BitDepth_8 :
+				{
+					BytesPerPixel    = PNG_NumColourComponents_Indexed;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_GreyAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		case PNG_ColourType_TrueAlpha :
+		{
+			switch(IHDR->BitDepth)
+			{
+				case PNG_BitDepth_8 :
+				case PNG_BitDepth_16 :
+				{
+					BytesPerPixel    = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
+					PixelsPerByte    = 1;
+
+					break;
+				}
+
+				default :
+				{
+					return(qfalse);
+				}
+			}
+
+			break;
+		}
+
+		default :
+		{
+			return(qfalse);
+		}
+	}
+
+	/*
+	 *  Calculate the size of the scanlines per pass
+	 */
+
+	for(a = 0; a < PNG_Adam7_NumPasses; a++)
+	{
+		BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
+	}
+
+	/*
+	 *  Calculate the size of all passes
+	 */
+
+	TargetLength = 0;
+
+	for(a = 0; a < PNG_Adam7_NumPasses; a++)
+	{
+		TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
+	}
+
+	/*
+	 *  Check if we have enough data for the whole image.
+	 */
+
+	if(!(DecompressedDataLength == TargetLength))
+	{
+		return(qfalse);
+	}
+
+	/*
+	 *  Unfilter the image.
+	 */
+
+	DecompPtr = DecompressedData;
+
+	for(a = 0; a < PNG_Adam7_NumPasses; a++)
+	{
+		if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel))
+		{
+			return(qfalse);
+		}
+
+		DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
+	}
+
+	/*
+	 *  Set the working pointers to the beginning of the buffers.
+	 */
+
+	DecompPtr = DecompressedData;
+
+	/*
+	 *  Create the output image.
+	 */
+
+	for(a = 0; a < PNG_Adam7_NumPasses; a++)
+	{
+		for(h = 0; h < PassHeight[a]; h++)
+		{
+			/*
+			 *  Count the pixels on the scanline for those multipixel bytes
+			 */
+
+			uint32_t CurrPixel;
+
+			/*
+			 *  skip FilterType
+			 *  but only when the pass has a width bigger than zero
+			 */
+
+			if(BytesPerScanline[a])
+			{
+				DecompPtr++;
+			}
+
+			/*
+			 *  Reset the pixel count.
+			 */
+
+			CurrPixel = 0;
+
+			for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++)
+			{
+				if(PixelsPerByte > 1)
+				{
+					uint8_t  Mask;
+					uint32_t Shift;
+					uint8_t  SinglePixel;
+
+					for(p = 0; p < PixelsPerByte; p++)
+					{
+						if(CurrPixel < PassWidth[a])
+						{
+							Mask  = (1 << IHDR->BitDepth) - 1;
+							Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
+
+							SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
+
+							OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
+
+							if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
+							{
+								return(qfalse);
+							}
+
+							CurrPixel++;
+						}
+					}
+
+				}
+				else
+				{
+					OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
+
+					if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
+					{
+						return(qfalse);
+					}
+				}
+
+				DecompPtr += BytesPerPixel;
+			}
+		}
+	}
+
+	return(qtrue);
+}
+
+/*
+ *  The PNG loader
+ */
+
+void R_LoadPNG(const char *name, byte **pic, int *width, int *height)
+{
+	struct BufferedFile *ThePNG;
+	byte *OutBuffer;
+	uint8_t *Signature;
+	struct PNG_ChunkHeader *CH;
+	uint32_t ChunkHeaderLength;
+	uint32_t ChunkHeaderType;
+	struct PNG_Chunk_IHDR *IHDR;
+	uint32_t IHDR_Width;
+	uint32_t IHDR_Height;
+	PNG_ChunkCRC *CRC;
+	uint8_t *InPal;
+	uint8_t *DecompressedData;
+	uint32_t DecompressedDataLength;
+	uint32_t i;
+
+	/*
+	 *  palette with 256 RGBA entries
+	 */
+
+	uint8_t OutPal[1024];
+
+	/*
+	 *  transparent colour from the tRNS chunk
+	 */
+
+	qboolean HasTransparentColour = qfalse;
+	uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+	/*
+	 *  input verification
+	 */
+
+	if(!(name && pic))
+	{
+		return;
+	}
+
+	/*
+	 *  Zero out return values.
+	 */
+
+	*pic = NULL;
+
+	if(width)
+	{
+		*width = 0;
+	}
+
+	if(height)
+	{
+		*height = 0;
+	}
+
+	/*
+	 *  Read the file.
+	 */
+
+	ThePNG = ReadBufferedFile(name);
+	if(!ThePNG)
+	{
+		return;
+	}           
+
+	/*
+	 *  Read the siganture of the file.
+	 */
+
+	Signature = BufferedFileRead(ThePNG, PNG_Signature_Size);
+	if(!Signature)
+	{
+		CloseBufferedFile(ThePNG);
+
+		return;
+	}
+
+	/*
+	 *  Is it a PNG?
+	 */
+
+	if(memcmp(Signature, PNG_Signature, PNG_Signature_Size))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Read the first chunk-header.
+	 */
+
+	CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+	if(!CH)
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  PNG multi-byte types are in Big Endian
+	 */
+
+	ChunkHeaderLength = BigLong(CH->Length);
+	ChunkHeaderType   = BigLong(CH->Type);
+
+	/*
+	 *  Check if the first chunk is an IHDR.
+	 */
+
+	if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size)))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Read the IHDR.
+	 */ 
+
+	IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size);
+	if(!IHDR)
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Read the CRC for IHDR
+	 */
+
+	CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+	if(!CRC)
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Here we could check the CRC if we wanted to.
+	 */
+
+	/*
+	 *  multi-byte type swapping
+	 */
+
+	IHDR_Width  = BigLong(IHDR->Width);
+	IHDR_Height = BigLong(IHDR->Height);
+
+	/*
+	 *  Check if Width and Height are valid.
+	 */
+
+	if(!((IHDR_Width > 0) && (IHDR_Height > 0))
+	|| IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height)
+	{
+		CloseBufferedFile(ThePNG);
+
+		ri.Printf( PRINT_WARNING, "%s: invalid image size\n", name );
+
+		return; 
+	}
+
+	/*
+	 *  Do we need to check if the dimensions of the image are valid for Quake3?
+	 */
+
+	/*
+	 *  Check if CompressionMethod and FilterMethod are valid.
+	 */
+
+	if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0)))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Check if InterlaceMethod is valid.
+	 */
+
+	if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced)  || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced)))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return;
+	}
+
+	/*
+	 *  Read palette for an indexed image.
+	 */
+
+	if(IHDR->ColourType == PNG_ColourType_Indexed)
+	{
+		/*
+		 *  We need the palette first.
+		 */
+
+		if(!FindChunk(ThePNG, PNG_ChunkType_PLTE))
+		{
+			CloseBufferedFile(ThePNG);
+
+			return;
+		}
+
+		/*
+		 *  Read the chunk-header.
+		 */
+
+		CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+		if(!CH)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  PNG multi-byte types are in Big Endian
+		 */
+
+		ChunkHeaderLength = BigLong(CH->Length);
+		ChunkHeaderType   = BigLong(CH->Type);
+
+		/*
+		 *  Check if the chunk is a PLTE.
+		 */
+
+		if(!(ChunkHeaderType == PNG_ChunkType_PLTE))
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  Check if Length is divisible by 3
+		 */
+
+		if(ChunkHeaderLength % 3)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return;   
+		}
+
+		/*
+		 *  Read the raw palette data
+		 */
+
+		InPal = BufferedFileRead(ThePNG, ChunkHeaderLength);
+		if(!InPal)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  Read the CRC for the palette
+		 */
+
+		CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+		if(!CRC)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  Set some default values.
+		 */
+
+		for(i = 0; i < 256; i++)
+		{
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00;
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00;
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00;
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;  
+		}
+
+		/*
+		 *  Convert to the Quake3 RGBA-format.
+		 */
+
+		for(i = 0; i < (ChunkHeaderLength / 3); i++)
+		{
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0];
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1];
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2];
+			OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;
+		}
+	}
+
+	/*
+	 *  transparency information is sometimes stored in a tRNS chunk
+	 */
+
+	/*
+	 *  Let's see if there is a tRNS chunk
+	 */
+
+	if(FindChunk(ThePNG, PNG_ChunkType_tRNS))
+	{
+		uint8_t *Trans;
+
+		/*
+		 *  Read the chunk-header.
+		 */
+
+		CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+		if(!CH)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  PNG multi-byte types are in Big Endian
+		 */
+
+		ChunkHeaderLength = BigLong(CH->Length);
+		ChunkHeaderType   = BigLong(CH->Type);
+
+		/*
+		 *  Check if the chunk is a tRNS.
+		 */
+
+		if(!(ChunkHeaderType == PNG_ChunkType_tRNS))
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  Read the transparency information.
+		 */
+
+		Trans = BufferedFileRead(ThePNG, ChunkHeaderLength);
+		if(!Trans)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return;  
+		}
+
+		/*
+		 *  Read the CRC.
+		 */
+
+		CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+		if(!CRC)
+		{
+			CloseBufferedFile(ThePNG);
+
+			return; 
+		}
+
+		/*
+		 *  Only for Grey, True and Indexed ColourType should tRNS exist.
+		 */
+
+		switch(IHDR->ColourType)
+		{
+			case PNG_ColourType_Grey :
+			{
+				if(!ChunkHeaderLength == 2)
+				{
+					CloseBufferedFile(ThePNG);
+
+					return;    
+				}
+
+				HasTransparentColour = qtrue;
+
+				/*
+				 *  Grey can have one colour which is completely transparent.
+				 *  This colour is always stored in 16 bits.
+				 */
+
+				TransparentColour[0] = Trans[0];
+				TransparentColour[1] = Trans[1];
+
+				break;
+			}
+
+			case PNG_ColourType_True :
+			{
+				if(!ChunkHeaderLength == 6)
+				{
+					CloseBufferedFile(ThePNG);
+
+					return;    
+				}
+
+				HasTransparentColour = qtrue;
+
+				/*
+				 *  True can have one colour which is completely transparent.
+				 *  This colour is always stored in 16 bits.
+				 */
+
+				TransparentColour[0] = Trans[0];
+				TransparentColour[1] = Trans[1];
+				TransparentColour[2] = Trans[2];
+				TransparentColour[3] = Trans[3];
+				TransparentColour[4] = Trans[4];
+				TransparentColour[5] = Trans[5];
+
+				break;
+			}
+
+			case PNG_ColourType_Indexed :
+			{
+				/*
+				 *  Maximum of 256 one byte transparency entries.
+				 */
+
+				if(ChunkHeaderLength > 256)
+				{
+					CloseBufferedFile(ThePNG);
+
+					return;    
+				}
+
+				HasTransparentColour = qtrue;
+
+				/*
+				 *  alpha values for palette entries
+				 */
+
+				for(i = 0; i < ChunkHeaderLength; i++)
+				{
+					OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i];
+				}
+
+				break;
+			}
+
+			/*
+			 *  All other ColourTypes should not have tRNS chunks
+			 */
+
+			default :
+			{
+				CloseBufferedFile(ThePNG);
+
+				return;
+			}
+		} 
+	}
+
+	/*
+	 *  Rewind to the start of the file.
+	 */
+
+	if(!BufferedFileRewind(ThePNG, -1))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Skip the signature
+	 */
+
+	if(!BufferedFileSkip(ThePNG, PNG_Signature_Size))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return; 
+	}
+
+	/*
+	 *  Decompress all IDAT chunks
+	 */
+
+	DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData);
+	if(!(DecompressedDataLength && DecompressedData))
+	{
+		CloseBufferedFile(ThePNG);
+
+		return;
+	}
+
+	/*
+	 *  Allocate output buffer.
+	 */
+
+	OutBuffer = ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL); 
+	if(!OutBuffer)
+	{
+		ri.Free(DecompressedData); 
+		CloseBufferedFile(ThePNG);
+
+		return;  
+	}
+
+	/*
+	 *  Interlaced and Non-interlaced images need to be handled differently.
+	 */
+
+	switch(IHDR->InterlaceMethod)
+	{
+		case PNG_InterlaceMethod_NonInterlaced :
+		{
+			if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
+			{
+				ri.Free(OutBuffer); 
+				ri.Free(DecompressedData); 
+				CloseBufferedFile(ThePNG);
+
+				return;
+			}
+
+			break;
+		}
+
+		case PNG_InterlaceMethod_Interlaced :
+		{
+			if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
+			{
+				ri.Free(OutBuffer); 
+				ri.Free(DecompressedData); 
+				CloseBufferedFile(ThePNG);
+
+				return;
+			}
+
+			break;
+		}
+
+		default :
+		{
+			ri.Free(OutBuffer); 
+			ri.Free(DecompressedData); 
+			CloseBufferedFile(ThePNG);
+
+			return;
+		}
+	}
+
+	/*
+	 *  update the pointer to the image data
+	 */
+
+	*pic = OutBuffer;
+
+	/*
+	 *  Fill width and height.
+	 */
+
+	if(width)
+	{
+		*width = IHDR_Width;
+	}
+
+	if(height)
+	{
+		*height = IHDR_Height;
+	}
+
+	/*
+	 *  DecompressedData is not needed anymore.
+	 */
+
+	ri.Free(DecompressedData); 
+
+	/*
+	 *  We have all data, so close the file.
+	 */
+
+	CloseBufferedFile(ThePNG);
+}

Added: trunk/code/rend2/tr_image_tga.c
===================================================================
--- trunk/code/rend2/tr_image_tga.c	                        (rev 0)
+++ trunk/code/rend2/tr_image_tga.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,324 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+extern	refimport_t		ri;
+
+/*
+========================================================================
+
+TGA files are used for 24/32 bit images
+
+========================================================================
+*/
+
+typedef struct _TargaHeader {
+	unsigned char 	id_length, colormap_type, image_type;
+	unsigned short	colormap_index, colormap_length;
+	unsigned char	colormap_size;
+	unsigned short	x_origin, y_origin, width, height;
+	unsigned char	pixel_size, attributes;
+} TargaHeader;
+
+void R_LoadTGA ( const char *name, byte **pic, int *width, int *height)
+{
+	unsigned	columns, rows, numPixels;
+	byte	*pixbuf;
+	int		row, column;
+	byte	*buf_p;
+	byte	*end;
+	union {
+		byte *b;
+		void *v;
+	} buffer;
+	TargaHeader	targa_header;
+	byte		*targa_rgba;
+	int length;
+
+	*pic = NULL;
+
+	if(width)
+		*width = 0;
+	if(height)
+		*height = 0;
+
+	//
+	// load the file
+	//
+	length = ri.FS_ReadFile ( ( char * ) name, &buffer.v);
+	if (!buffer.b || length < 0) {
+		return;
+	}
+
+	if(length < 18)
+	{
+		ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name );
+	}
+
+	buf_p = buffer.b;
+	end = buffer.b + length;
+
+	targa_header.id_length = buf_p[0];
+	targa_header.colormap_type = buf_p[1];
+	targa_header.image_type = buf_p[2];
+	
+	memcpy(&targa_header.colormap_index, &buf_p[3], 2);
+	memcpy(&targa_header.colormap_length, &buf_p[5], 2);
+	targa_header.colormap_size = buf_p[7];
+	memcpy(&targa_header.x_origin, &buf_p[8], 2);
+	memcpy(&targa_header.y_origin, &buf_p[10], 2);
+	memcpy(&targa_header.width, &buf_p[12], 2);
+	memcpy(&targa_header.height, &buf_p[14], 2);
+	targa_header.pixel_size = buf_p[16];
+	targa_header.attributes = buf_p[17];
+
+	targa_header.colormap_index = LittleShort(targa_header.colormap_index);
+	targa_header.colormap_length = LittleShort(targa_header.colormap_length);
+	targa_header.x_origin = LittleShort(targa_header.x_origin);
+	targa_header.y_origin = LittleShort(targa_header.y_origin);
+	targa_header.width = LittleShort(targa_header.width);
+	targa_header.height = LittleShort(targa_header.height);
+
+	buf_p += 18;
+
+	if (targa_header.image_type!=2 
+		&& targa_header.image_type!=10
+		&& targa_header.image_type != 3 ) 
+	{
+		ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported");
+	}
+
+	if ( targa_header.colormap_type != 0 )
+	{
+		ri.Error( ERR_DROP, "LoadTGA: colormaps not supported" );
+	}
+
+	if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 )
+	{
+		ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)");
+	}
+
+	columns = targa_header.width;
+	rows = targa_header.height;
+	numPixels = columns * rows * 4;
+
+	if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows)
+	{
+		ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size", name);
+	}
+
+
+	targa_rgba = ri.Malloc (numPixels);
+
+	if (targa_header.id_length != 0)
+	{
+		if (buf_p + targa_header.id_length > end)
+			ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name );
+
+		buf_p += targa_header.id_length;  // skip TARGA image comment
+	}
+	
+	if ( targa_header.image_type==2 || targa_header.image_type == 3 )
+	{ 
+		if(buf_p + columns*rows*targa_header.pixel_size/8 > end)
+		{
+			ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name);
+		}
+
+		// Uncompressed RGB or gray scale image
+		for(row=rows-1; row>=0; row--) 
+		{
+			pixbuf = targa_rgba + row*columns*4;
+			for(column=0; column<columns; column++) 
+			{
+				unsigned char red,green,blue,alphabyte;
+				switch (targa_header.pixel_size) 
+				{
+					
+				case 8:
+					blue = *buf_p++;
+					green = blue;
+					red = blue;
+					*pixbuf++ = red;
+					*pixbuf++ = green;
+					*pixbuf++ = blue;
+					*pixbuf++ = 255;
+					break;
+
+				case 24:
+					blue = *buf_p++;
+					green = *buf_p++;
+					red = *buf_p++;
+					*pixbuf++ = red;
+					*pixbuf++ = green;
+					*pixbuf++ = blue;
+					*pixbuf++ = 255;
+					break;
+				case 32:
+					blue = *buf_p++;
+					green = *buf_p++;
+					red = *buf_p++;
+					alphabyte = *buf_p++;
+					*pixbuf++ = red;
+					*pixbuf++ = green;
+					*pixbuf++ = blue;
+					*pixbuf++ = alphabyte;
+					break;
+				default:
+					ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'", targa_header.pixel_size, name );
+					break;
+				}
+			}
+		}
+	}
+	else if (targa_header.image_type==10) {   // Runlength encoded RGB images
+		unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
+
+		red = 0;
+		green = 0;
+		blue = 0;
+		alphabyte = 0xff;
+
+		for(row=rows-1; row>=0; row--) {
+			pixbuf = targa_rgba + row*columns*4;
+			for(column=0; column<columns; ) {
+				if(buf_p + 1 > end)
+					ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name);
+				packetHeader= *buf_p++;
+				packetSize = 1 + (packetHeader & 0x7f);
+				if (packetHeader & 0x80) {        // run-length packet
+					if(buf_p + targa_header.pixel_size/8 > end)
+						ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name);
+					switch (targa_header.pixel_size) {
+						case 24:
+								blue = *buf_p++;
+								green = *buf_p++;
+								red = *buf_p++;
+								alphabyte = 255;
+								break;
+						case 32:
+								blue = *buf_p++;
+								green = *buf_p++;
+								red = *buf_p++;
+								alphabyte = *buf_p++;
+								break;
+						default:
+							ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'", targa_header.pixel_size, name );
+							break;
+					}
+	
+					for(j=0;j<packetSize;j++) {
+						*pixbuf++=red;
+						*pixbuf++=green;
+						*pixbuf++=blue;
+						*pixbuf++=alphabyte;
+						column++;
+						if (column==columns) { // run spans across rows
+							column=0;
+							if (row>0)
+								row--;
+							else
+								goto breakOut;
+							pixbuf = targa_rgba + row*columns*4;
+						}
+					}
+				}
+				else {                            // non run-length packet
+
+					if(buf_p + targa_header.pixel_size/8*packetSize > end)
+						ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name);
+					for(j=0;j<packetSize;j++) {
+						switch (targa_header.pixel_size) {
+							case 24:
+									blue = *buf_p++;
+									green = *buf_p++;
+									red = *buf_p++;
+									*pixbuf++ = red;
+									*pixbuf++ = green;
+									*pixbuf++ = blue;
+									*pixbuf++ = 255;
+									break;
+							case 32:
+									blue = *buf_p++;
+									green = *buf_p++;
+									red = *buf_p++;
+									alphabyte = *buf_p++;
+									*pixbuf++ = red;
+									*pixbuf++ = green;
+									*pixbuf++ = blue;
+									*pixbuf++ = alphabyte;
+									break;
+							default:
+								ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'", targa_header.pixel_size, name );
+								break;
+						}
+						column++;
+						if (column==columns) { // pixel packet run spans across rows
+							column=0;
+							if (row>0)
+								row--;
+							else
+								goto breakOut;
+							pixbuf = targa_rgba + row*columns*4;
+						}						
+					}
+				}
+			}
+			breakOut:;
+		}
+	}
+
+#if 0 
+  // TTimo: this is the chunk of code to ensure a behavior that meets TGA specs 
+  // bit 5 set => top-down
+  if (targa_header.attributes & 0x20) {
+    unsigned char *flip = (unsigned char*)malloc (columns*4);
+    unsigned char *src, *dst;
+
+    for (row = 0; row < rows/2; row++) {
+      src = targa_rgba + row * 4 * columns;
+      dst = targa_rgba + (rows - row - 1) * 4 * columns;
+
+      memcpy (flip, src, columns*4);
+      memcpy (src, dst, columns*4);
+      memcpy (dst, flip, columns*4);
+    }
+    free (flip);
+  }
+#endif
+  // instead we just print a warning
+  if (targa_header.attributes & 0x20) {
+    ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name);
+  }
+
+  if (width)
+	  *width = columns;
+  if (height)
+	  *height = rows;
+
+  *pic = targa_rgba;
+
+  ri.FS_FreeFile (buffer.v);
+}

Added: trunk/code/rend2/tr_init.c
===================================================================
--- trunk/code/rend2/tr_init.c	                        (rev 0)
+++ trunk/code/rend2/tr_init.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1560 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_init.c -- functions that are not called every frame
+
+#include "tr_local.h"
+
+glconfig_t  glConfig;
+glRefConfig_t glRefConfig;
+qboolean    textureFilterAnisotropic = qfalse;
+int         maxAnisotropy = 0;
+float       displayAspect = 0.0f;
+
+glstate_t	glState;
+
+static void GfxInfo_f( void );
+static void GfxMemInfo_f( void );
+
+#ifdef USE_RENDERER_DLOPEN
+cvar_t  *com_altivec;
+#endif
+
+cvar_t	*r_flareSize;
+cvar_t	*r_flareFade;
+cvar_t	*r_flareCoeff;
+
+cvar_t	*r_railWidth;
+cvar_t	*r_railCoreWidth;
+cvar_t	*r_railSegmentLength;
+
+cvar_t	*r_verbose;
+cvar_t	*r_ignore;
+
+cvar_t	*r_detailTextures;
+
+cvar_t	*r_znear;
+cvar_t	*r_zproj;
+cvar_t	*r_stereoSeparation;
+
+cvar_t	*r_smp;
+cvar_t	*r_showSmp;
+cvar_t	*r_skipBackEnd;
+
+cvar_t	*r_stereoEnabled;
+cvar_t	*r_anaglyphMode;
+
+cvar_t	*r_greyscale;
+
+cvar_t	*r_ignorehwgamma;
+cvar_t	*r_measureOverdraw;
+
+cvar_t	*r_inGameVideo;
+cvar_t	*r_fastsky;
+cvar_t	*r_drawSun;
+cvar_t	*r_dynamiclight;
+cvar_t	*r_dlightBacks;
+
+cvar_t	*r_lodbias;
+cvar_t	*r_lodscale;
+
+cvar_t	*r_norefresh;
+cvar_t	*r_drawentities;
+cvar_t	*r_drawworld;
+cvar_t	*r_speeds;
+cvar_t	*r_fullbright;
+cvar_t	*r_novis;
+cvar_t	*r_nocull;
+cvar_t	*r_facePlaneCull;
+cvar_t	*r_showcluster;
+cvar_t	*r_nocurves;
+
+cvar_t	*r_allowExtensions;
+
+cvar_t	*r_ext_compressed_textures;
+cvar_t	*r_ext_multitexture;
+cvar_t	*r_ext_compiled_vertex_array;
+cvar_t	*r_ext_texture_env_add;
+cvar_t	*r_ext_texture_filter_anisotropic;
+cvar_t	*r_ext_max_anisotropy;
+
+cvar_t  *r_ext_draw_range_elements;
+cvar_t  *r_ext_multi_draw_arrays;
+cvar_t  *r_ext_framebuffer_object;
+cvar_t  *r_ext_texture_float;
+cvar_t  *r_arb_half_float_pixel;
+cvar_t  *r_ext_framebuffer_multisample;
+
+cvar_t  *r_mergeMultidraws;
+cvar_t  *r_mergeLeafSurfaces;
+
+cvar_t  *r_cameraExposure;
+cvar_t  *r_hdr;
+cvar_t  *r_postProcess;
+
+cvar_t  *r_toneMap;
+cvar_t  *r_forceToneMap;
+cvar_t  *r_forceToneMapMin;
+cvar_t  *r_forceToneMapAvg;
+cvar_t  *r_forceToneMapMax;
+
+cvar_t  *r_autoExposure;
+cvar_t  *r_forceAutoExposure;
+cvar_t  *r_forceAutoExposureMin;
+cvar_t  *r_forceAutoExposureMax;
+
+cvar_t  *r_srgb;
+
+cvar_t  *r_depthPrepass;
+cvar_t  *r_ssao;
+
+cvar_t  *r_normalMapping;
+cvar_t  *r_specularMapping;
+cvar_t  *r_deluxeMapping;
+cvar_t  *r_parallaxMapping;
+cvar_t  *r_normalAmbient;
+cvar_t  *r_recalcMD3Normals;
+cvar_t  *r_mergeLightmaps;
+cvar_t  *r_dlightMode;
+cvar_t  *r_pshadowDist;
+cvar_t  *r_imageUpsample;
+cvar_t  *r_imageUpsampleMaxSize;
+cvar_t  *r_imageUpsampleType;
+cvar_t  *r_genNormalMaps;
+cvar_t  *r_forceSun;
+cvar_t  *r_forceSunMapLightScale;
+cvar_t  *r_forceSunLightScale;
+cvar_t  *r_forceSunAmbientScale;
+cvar_t  *r_sunShadows;
+cvar_t  *r_shadowFilter;
+cvar_t  *r_shadowMapSize;
+cvar_t  *r_shadowCascadeZNear;
+cvar_t  *r_shadowCascadeZFar;
+cvar_t  *r_shadowCascadeZBias;
+
+cvar_t	*r_ignoreGLErrors;
+cvar_t	*r_logFile;
+
+cvar_t	*r_stencilbits;
+cvar_t	*r_depthbits;
+cvar_t	*r_colorbits;
+cvar_t	*r_texturebits;
+cvar_t  *r_ext_multisample;
+
+cvar_t	*r_drawBuffer;
+cvar_t	*r_lightmap;
+cvar_t	*r_vertexLight;
+cvar_t	*r_uiFullScreen;
+cvar_t	*r_shadows;
+cvar_t	*r_flares;
+cvar_t	*r_mode;
+cvar_t	*r_nobind;
+cvar_t	*r_singleShader;
+cvar_t	*r_roundImagesDown;
+cvar_t	*r_colorMipLevels;
+cvar_t	*r_picmip;
+cvar_t	*r_showtris;
+cvar_t	*r_showsky;
+cvar_t	*r_shownormals;
+cvar_t	*r_finish;
+cvar_t	*r_clear;
+cvar_t	*r_swapInterval;
+cvar_t	*r_textureMode;
+cvar_t	*r_offsetFactor;
+cvar_t	*r_offsetUnits;
+cvar_t	*r_gamma;
+cvar_t	*r_intensity;
+cvar_t	*r_lockpvs;
+cvar_t	*r_noportals;
+cvar_t	*r_portalOnly;
+
+cvar_t	*r_subdivisions;
+cvar_t	*r_lodCurveError;
+
+cvar_t	*r_fullscreen;
+cvar_t  *r_noborder;
+
+cvar_t	*r_customwidth;
+cvar_t	*r_customheight;
+cvar_t	*r_customPixelAspect;
+
+cvar_t	*r_overBrightBits;
+cvar_t	*r_mapOverBrightBits;
+
+cvar_t	*r_debugSurface;
+cvar_t	*r_simpleMipMaps;
+
+cvar_t	*r_showImages;
+
+cvar_t	*r_ambientScale;
+cvar_t	*r_directedScale;
+cvar_t	*r_debugLight;
+cvar_t	*r_debugSort;
+cvar_t	*r_printShaders;
+cvar_t	*r_saveFontData;
+
+cvar_t	*r_marksOnTriangleMeshes;
+
+cvar_t	*r_aviMotionJpegQuality;
+cvar_t	*r_screenshotJpegQuality;
+
+cvar_t	*r_maxpolys;
+int		max_polys;
+cvar_t	*r_maxpolyverts;
+int		max_polyverts;
+
+/*
+** InitOpenGL
+**
+** This function is responsible for initializing a valid OpenGL subsystem.  This
+** is done by calling GLimp_Init (which gives us a working OGL subsystem) then
+** setting variables, checking GL constants, and reporting the gfx system config
+** to the user.
+*/
+static void InitOpenGL( void )
+{
+	char renderer_buffer[1024];
+
+	//
+	// initialize OS specific portions of the renderer
+	//
+	// GLimp_Init directly or indirectly references the following cvars:
+	//		- r_fullscreen
+	//		- r_mode
+	//		- r_(color|depth|stencil)bits
+	//		- r_ignorehwgamma
+	//		- r_gamma
+	//
+	
+	if ( glConfig.vidWidth == 0 )
+	{
+		GLint		temp;
+		
+		GLimp_Init();
+		GLimp_InitExtraExtensions();
+
+		strcpy( renderer_buffer, glConfig.renderer_string );
+		Q_strlwr( renderer_buffer );
+
+		// OpenGL driver constants
+		qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp );
+		glConfig.maxTextureSize = temp;
+
+		// stubbed or broken drivers may have reported 0...
+		if ( glConfig.maxTextureSize <= 0 ) 
+		{
+			glConfig.maxTextureSize = 0;
+		}
+	}
+
+	// init command buffers and SMP
+	R_InitCommandBuffers();
+
+	// set default state
+	GL_SetDefaultState();
+}
+
+/*
+==================
+GL_CheckErrors
+==================
+*/
+void GL_CheckErrs( char *file, int line ) {
+	int		err;
+	char	s[64];
+
+	err = qglGetError();
+	if ( err == GL_NO_ERROR ) {
+		return;
+	}
+	if ( r_ignoreGLErrors->integer ) {
+		return;
+	}
+	switch( err ) {
+		case GL_INVALID_ENUM:
+			strcpy( s, "GL_INVALID_ENUM" );
+			break;
+		case GL_INVALID_VALUE:
+			strcpy( s, "GL_INVALID_VALUE" );
+			break;
+		case GL_INVALID_OPERATION:
+			strcpy( s, "GL_INVALID_OPERATION" );
+			break;
+		case GL_STACK_OVERFLOW:
+			strcpy( s, "GL_STACK_OVERFLOW" );
+			break;
+		case GL_STACK_UNDERFLOW:
+			strcpy( s, "GL_STACK_UNDERFLOW" );
+			break;
+		case GL_OUT_OF_MEMORY:
+			strcpy( s, "GL_OUT_OF_MEMORY" );
+			break;
+		default:
+			Com_sprintf( s, sizeof(s), "%i", err);
+			break;
+	}
+
+	ri.Error( ERR_FATAL, "GL_CheckErrors: %s in %s at line %d", s , file, line);
+}
+
+
+/*
+** R_GetModeInfo
+*/
+typedef struct vidmode_s
+{
+	const char *description;
+	int width, height;
+	float pixelAspect;		// pixel width / height
+} vidmode_t;
+
+vidmode_t r_vidModes[] =
+{
+	{ "Mode  0: 320x240",		320,	240,	1 },
+	{ "Mode  1: 400x300",		400,	300,	1 },
+	{ "Mode  2: 512x384",		512,	384,	1 },
+	{ "Mode  3: 640x480",		640,	480,	1 },
+	{ "Mode  4: 800x600",		800,	600,	1 },
+	{ "Mode  5: 960x720",		960,	720,	1 },
+	{ "Mode  6: 1024x768",		1024,	768,	1 },
+	{ "Mode  7: 1152x864",		1152,	864,	1 },
+	{ "Mode  8: 1280x1024",		1280,	1024,	1 },
+	{ "Mode  9: 1600x1200",		1600,	1200,	1 },
+	{ "Mode 10: 2048x1536",		2048,	1536,	1 },
+	{ "Mode 11: 856x480 (wide)",856,	480,	1 }
+};
+static int	s_numVidModes = ARRAY_LEN( r_vidModes );
+
+qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ) {
+	vidmode_t	*vm;
+	float			pixelAspect;
+
+	if ( mode < -1 ) {
+		return qfalse;
+	}
+	if ( mode >= s_numVidModes ) {
+		return qfalse;
+	}
+
+	if ( mode == -1 ) {
+		*width = r_customwidth->integer;
+		*height = r_customheight->integer;
+		pixelAspect = r_customPixelAspect->value;
+	} else {
+		vm = &r_vidModes[mode];
+
+		*width  = vm->width;
+		*height = vm->height;
+		pixelAspect = vm->pixelAspect;
+	}
+
+	*windowAspect = (float)*width / ( *height * pixelAspect );
+
+	return qtrue;
+}
+
+/*
+** R_ModeList_f
+*/
+static void R_ModeList_f( void )
+{
+	int i;
+
+	ri.Printf( PRINT_ALL, "\n" );
+	for ( i = 0; i < s_numVidModes; i++ )
+	{
+		ri.Printf( PRINT_ALL, "%s\n", r_vidModes[i].description );
+	}
+	ri.Printf( PRINT_ALL, "\n" );
+}
+
+
+/* 
+============================================================================== 
+ 
+						SCREEN SHOTS 
+
+NOTE TTimo
+some thoughts about the screenshots system:
+screenshots get written in fs_homepath + fs_gamedir
+vanilla q3 .. baseq3/screenshots/ *.tga
+team arena .. missionpack/screenshots/ *.tga
+
+two commands: "screenshot" and "screenshotJPEG"
+we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available
+(with FS_FileExists / FS_FOpenFileWrite calls)
+FIXME: the statics don't get a reinit between fs_game changes
+
+============================================================================== 
+*/ 
+
+/* 
+================== 
+RB_ReadPixels
+
+Reads an image but takes care of alignment issues for reading RGB images.
+
+Reads a minimum offset for where the RGB data starts in the image from
+integer stored at pointer offset. When the function has returned the actual
+offset was written back to address offset. This address will always have an
+alignment of packAlign to ensure efficient copying.
+
+Stores the length of padding after a line of pixels to address padlen
+
+Return value must be freed with ri.Hunk_FreeTempMemory()
+================== 
+*/  
+
+byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen)
+{
+	byte *buffer, *bufstart;
+	int padwidth, linelen;
+	GLint packAlign;
+	
+	qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
+	
+	linelen = width * 3;
+	padwidth = PAD(linelen, packAlign);
+	
+	// Allocate a few more bytes so that we can choose an alignment we like
+	buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1);
+	
+	bufstart = PADP((intptr_t) buffer + *offset, packAlign);
+	qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart);
+	
+	*offset = bufstart - buffer;
+	*padlen = padwidth - linelen;
+	
+	return buffer;
+}
+
+/* 
+================== 
+RB_TakeScreenshot
+================== 
+*/  
+void RB_TakeScreenshot(int x, int y, int width, int height, char *fileName)
+{
+	byte *allbuf, *buffer;
+	byte *srcptr, *destptr;
+	byte *endline, *endmem;
+	byte temp;
+	
+	int linelen, padlen;
+	size_t offset = 18, memcount;
+		
+	allbuf = RB_ReadPixels(x, y, width, height, &offset, &padlen);
+	buffer = allbuf + offset - 18;
+	
+	Com_Memset (buffer, 0, 18);
+	buffer[2] = 2;		// uncompressed type
+	buffer[12] = width & 255;
+	buffer[13] = width >> 8;
+	buffer[14] = height & 255;
+	buffer[15] = height >> 8;
+	buffer[16] = 24;	// pixel size
+
+	// swap rgb to bgr and remove padding from line endings
+	linelen = width * 3;
+	
+	srcptr = destptr = allbuf + offset;
+	endmem = srcptr + (linelen + padlen) * height;
+	
+	while(srcptr < endmem)
+	{
+		endline = srcptr + linelen;
+
+		while(srcptr < endline)
+		{
+			temp = srcptr[0];
+			*destptr++ = srcptr[2];
+			*destptr++ = srcptr[1];
+			*destptr++ = temp;
+			
+			srcptr += 3;
+		}
+		
+		// Skip the pad
+		srcptr += padlen;
+	}
+
+	memcount = linelen * height;
+
+	// gamma correct
+	if(glConfig.deviceSupportsGamma)
+		R_GammaCorrect(allbuf + offset, memcount);
+
+	ri.FS_WriteFile(fileName, buffer, memcount + 18);
+
+	ri.Hunk_FreeTempMemory(allbuf);
+}
+
+/* 
+================== 
+RB_TakeScreenshotJPEG
+================== 
+*/
+
+void RB_TakeScreenshotJPEG(int x, int y, int width, int height, char *fileName)
+{
+	byte *buffer;
+	size_t offset = 0, memcount;
+	int padlen;
+
+	buffer = RB_ReadPixels(x, y, width, height, &offset, &padlen);
+	memcount = (width * 3 + padlen) * height;
+
+	// gamma correct
+	if(glConfig.deviceSupportsGamma)
+		R_GammaCorrect(buffer + offset, memcount);
+
+	RE_SaveJPG(fileName, r_screenshotJpegQuality->integer, width, height, buffer + offset, padlen);
+	ri.Hunk_FreeTempMemory(buffer);
+}
+
+/*
+==================
+RB_TakeScreenshotCmd
+==================
+*/
+const void *RB_TakeScreenshotCmd( const void *data ) {
+	const screenshotCommand_t	*cmd;
+	
+	cmd = (const screenshotCommand_t *)data;
+	
+	if (cmd->jpeg)
+		RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName);
+	else
+		RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName);
+	
+	return (const void *)(cmd + 1);	
+}
+
+/*
+==================
+R_TakeScreenshot
+==================
+*/
+void R_TakeScreenshot( int x, int y, int width, int height, char *name, qboolean jpeg ) {
+	static char	fileName[MAX_OSPATH]; // bad things if two screenshots per frame?
+	screenshotCommand_t	*cmd;
+
+	cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+	if ( !cmd ) {
+		return;
+	}
+	cmd->commandId = RC_SCREENSHOT;
+
+	cmd->x = x;
+	cmd->y = y;
+	cmd->width = width;
+	cmd->height = height;
+	Q_strncpyz( fileName, name, sizeof(fileName) );
+	cmd->fileName = fileName;
+	cmd->jpeg = jpeg;
+}
+
+/* 
+================== 
+R_ScreenshotFilename
+================== 
+*/  
+void R_ScreenshotFilename( int lastNumber, char *fileName ) {
+	int		a,b,c,d;
+
+	if ( lastNumber < 0 || lastNumber > 9999 ) {
+		Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" );
+		return;
+	}
+
+	a = lastNumber / 1000;
+	lastNumber -= a*1000;
+	b = lastNumber / 100;
+	lastNumber -= b*100;
+	c = lastNumber / 10;
+	lastNumber -= c*10;
+	d = lastNumber;
+
+	Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga"
+		, a, b, c, d );
+}
+
+/* 
+================== 
+R_ScreenshotFilename
+================== 
+*/  
+void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) {
+	int		a,b,c,d;
+
+	if ( lastNumber < 0 || lastNumber > 9999 ) {
+		Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" );
+		return;
+	}
+
+	a = lastNumber / 1000;
+	lastNumber -= a*1000;
+	b = lastNumber / 100;
+	lastNumber -= b*100;
+	c = lastNumber / 10;
+	lastNumber -= c*10;
+	d = lastNumber;
+
+	Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg"
+		, a, b, c, d );
+}
+
+/*
+====================
+R_LevelShot
+
+levelshots are specialized 128*128 thumbnails for
+the menu system, sampled down from full screen distorted images
+====================
+*/
+void R_LevelShot( void ) {
+	char		checkname[MAX_OSPATH];
+	byte		*buffer;
+	byte		*source, *allsource;
+	byte		*src, *dst;
+	size_t			offset = 0;
+	int			padlen;
+	int			x, y;
+	int			r, g, b;
+	float		xScale, yScale;
+	int			xx, yy;
+
+	Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName);
+
+	allsource = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen);
+	source = allsource + offset;
+
+	buffer = ri.Hunk_AllocateTempMemory(128 * 128*3 + 18);
+	Com_Memset (buffer, 0, 18);
+	buffer[2] = 2;		// uncompressed type
+	buffer[12] = 128;
+	buffer[14] = 128;
+	buffer[16] = 24;	// pixel size
+
+	// resample from source
+	xScale = glConfig.vidWidth / 512.0f;
+	yScale = glConfig.vidHeight / 384.0f;
+	for ( y = 0 ; y < 128 ; y++ ) {
+		for ( x = 0 ; x < 128 ; x++ ) {
+			r = g = b = 0;
+			for ( yy = 0 ; yy < 3 ; yy++ ) {
+				for ( xx = 0 ; xx < 4 ; xx++ ) {
+					src = source + (3 * glConfig.vidWidth + padlen) * (int)((y*3 + yy) * yScale) +
+						3 * (int) ((x*4 + xx) * xScale);
+					r += src[0];
+					g += src[1];
+					b += src[2];
+				}
+			}
+			dst = buffer + 18 + 3 * ( y * 128 + x );
+			dst[0] = b / 12;
+			dst[1] = g / 12;
+			dst[2] = r / 12;
+		}
+	}
+
+	// gamma correct
+	if ( glConfig.deviceSupportsGamma ) {
+		R_GammaCorrect( buffer + 18, 128 * 128 * 3 );
+	}
+
+	ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 );
+
+	ri.Hunk_FreeTempMemory(buffer);
+	ri.Hunk_FreeTempMemory(allsource);
+
+	ri.Printf( PRINT_ALL, "Wrote %s\n", checkname );
+}
+
+/* 
+================== 
+R_ScreenShot_f
+
+screenshot
+screenshot [silent]
+screenshot [levelshot]
+screenshot [filename]
+
+Doesn't print the pacifier message if there is a second arg
+================== 
+*/  
+void R_ScreenShot_f (void) {
+	char	checkname[MAX_OSPATH];
+	static	int	lastNumber = -1;
+	qboolean	silent;
+
+	if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+		R_LevelShot();
+		return;
+	}
+
+	if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+		silent = qtrue;
+	} else {
+		silent = qfalse;
+	}
+
+	if ( ri.Cmd_Argc() == 2 && !silent ) {
+		// explicit filename
+		Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) );
+	} else {
+		// scan for a free filename
+
+		// if we have saved a previous screenshot, don't scan
+		// again, because recording demo avis can involve
+		// thousands of shots
+		if ( lastNumber == -1 ) {
+			lastNumber = 0;
+		}
+		// scan for a free number
+		for ( ; lastNumber <= 9999 ; lastNumber++ ) {
+			R_ScreenshotFilename( lastNumber, checkname );
+
+      if (!ri.FS_FileExists( checkname ))
+      {
+        break; // file doesn't exist
+      }
+		}
+
+		if ( lastNumber >= 9999 ) {
+			ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); 
+			return;
+ 		}
+
+		lastNumber++;
+	}
+
+	R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qfalse );
+
+	if ( !silent ) {
+		ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+	}
+} 
+
+void R_ScreenShotJPEG_f (void) {
+	char		checkname[MAX_OSPATH];
+	static	int	lastNumber = -1;
+	qboolean	silent;
+
+	if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+		R_LevelShot();
+		return;
+	}
+
+	if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+		silent = qtrue;
+	} else {
+		silent = qfalse;
+	}
+
+	if ( ri.Cmd_Argc() == 2 && !silent ) {
+		// explicit filename
+		Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) );
+	} else {
+		// scan for a free filename
+
+		// if we have saved a previous screenshot, don't scan
+		// again, because recording demo avis can involve
+		// thousands of shots
+		if ( lastNumber == -1 ) {
+			lastNumber = 0;
+		}
+		// scan for a free number
+		for ( ; lastNumber <= 9999 ; lastNumber++ ) {
+			R_ScreenshotFilenameJPEG( lastNumber, checkname );
+
+      if (!ri.FS_FileExists( checkname ))
+      {
+        break; // file doesn't exist
+      }
+		}
+
+		if ( lastNumber == 10000 ) {
+			ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); 
+			return;
+ 		}
+
+		lastNumber++;
+	}
+
+	R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qtrue );
+
+	if ( !silent ) {
+		ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+	}
+} 
+
+//============================================================================
+
+/*
+==================
+RB_TakeVideoFrameCmd
+==================
+*/
+const void *RB_TakeVideoFrameCmd( const void *data )
+{
+	const videoFrameCommand_t	*cmd;
+	byte				*cBuf;
+	size_t				memcount, linelen;
+	int				padwidth, avipadwidth, padlen, avipadlen;
+	GLint packAlign;
+	
+	cmd = (const videoFrameCommand_t *)data;
+	
+	qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
+
+	linelen = cmd->width * 3;
+
+	// Alignment stuff for glReadPixels
+	padwidth = PAD(linelen, packAlign);
+	padlen = padwidth - linelen;
+	// AVI line padding
+	avipadwidth = PAD(linelen, AVI_LINE_PADDING);
+	avipadlen = avipadwidth - linelen;
+
+	cBuf = PADP(cmd->captureBuffer, packAlign);
+		
+	qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB,
+		GL_UNSIGNED_BYTE, cBuf);
+
+	memcount = padwidth * cmd->height;
+
+	// gamma correct
+	if(glConfig.deviceSupportsGamma)
+		R_GammaCorrect(cBuf, memcount);
+
+	if(cmd->motionJpeg)
+	{
+		memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height,
+			r_aviMotionJpegQuality->integer,
+			cmd->width, cmd->height, cBuf, padlen);
+		ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount);
+	}
+	else
+	{
+		byte *lineend, *memend;
+		byte *srcptr, *destptr;
+	
+		srcptr = cBuf;
+		destptr = cmd->encodeBuffer;
+		memend = srcptr + memcount;
+		
+		// swap R and B and remove line paddings
+		while(srcptr < memend)
+		{
+			lineend = srcptr + linelen;
+			while(srcptr < lineend)
+			{
+				*destptr++ = srcptr[2];
+				*destptr++ = srcptr[1];
+				*destptr++ = srcptr[0];
+				srcptr += 3;
+			}
+			
+			Com_Memset(destptr, '\0', avipadlen);
+			destptr += avipadlen;
+			
+			srcptr += padlen;
+		}
+		
+		ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, avipadwidth * cmd->height);
+	}
+
+	return (const void *)(cmd + 1);	
+}
+
+//============================================================================
+
+/*
+** GL_SetDefaultState
+*/
+void GL_SetDefaultState( void )
+{
+	qglClearDepth( 1.0f );
+
+	qglCullFace(GL_FRONT);
+
+	qglColor4f (1,1,1,1);
+
+	// initialize downstream texture unit if we're running
+	// in a multitexture environment
+	if ( qglActiveTextureARB ) {
+		GL_SelectTexture( 1 );
+		GL_TextureMode( r_textureMode->string );
+		GL_TexEnv( GL_MODULATE );
+		qglDisable( GL_TEXTURE_2D );
+		GL_SelectTexture( 0 );
+	}
+
+	qglEnable(GL_TEXTURE_2D);
+	GL_TextureMode( r_textureMode->string );
+	GL_TexEnv( GL_MODULATE );
+
+	//qglShadeModel( GL_SMOOTH );
+	qglDepthFunc( GL_LEQUAL );
+
+	//
+	// make sure our GL state vector is set correctly
+	//
+	glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
+
+	glState.vertexAttribsState = 0;
+	glState.vertexAttribPointersSet = 0;
+	glState.currentProgram = 0;
+	qglUseProgramObjectARB(0);
+
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+	glState.currentVBO = NULL;
+	glState.currentIBO = NULL;
+
+	if (glRefConfig.framebuffer_srgb)
+	{
+		qglEnable(GL_FRAMEBUFFER_SRGB_EXT);
+	}
+
+	qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
+	qglDepthMask( GL_TRUE );
+	qglDisable( GL_DEPTH_TEST );
+	qglEnable( GL_SCISSOR_TEST );
+	qglDisable( GL_CULL_FACE );
+	qglDisable( GL_BLEND );
+}
+
+
+/*
+================
+GfxInfo_f
+================
+*/
+void GfxInfo_f( void ) 
+{
+	const char *enablestrings[] =
+	{
+		"disabled",
+		"enabled"
+	};
+	const char *fsstrings[] =
+	{
+		"windowed",
+		"fullscreen"
+	};
+
+	ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string );
+	ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string );
+	ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string );
+	// this was really bugging me
+	if (strlen(glConfig.extensions_string) > 1008)
+	{
+		char buffer[1024];
+		char *p;
+		int size = strlen(glConfig.extensions_string);
+		ri.Printf( PRINT_ALL, "GL_EXTENSIONS: ");
+
+		p = glConfig.extensions_string;
+		while(size > 0)
+		{
+			Q_strncpyz(buffer, p, 1024);
+			ri.Printf( PRINT_ALL, "%s", buffer );
+			p += 1023;
+			size -= 1023;
+		}
+		ri.Printf( PRINT_ALL, "\n" );
+	}
+	else
+	{
+		ri.Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string );
+	}
+	ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize );
+	ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits );
+	ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits );
+	ri.Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] );
+	if ( glConfig.displayFrequency )
+	{
+		ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency );
+	}
+	else
+	{
+		ri.Printf( PRINT_ALL, "N/A\n" );
+	}
+	if ( glConfig.deviceSupportsGamma )
+	{
+		ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits );
+	}
+	else
+	{
+		ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits );
+	}
+
+	ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string );
+	ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer );
+	ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer );
+	ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] );
+	ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] );
+	ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] );
+	ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] );
+	if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 )
+	{
+		ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" );
+	}
+	if ( glConfig.hardwareType == GLHW_RAGEPRO )
+	{
+		ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" );
+	}
+	if ( glConfig.hardwareType == GLHW_RIVA128 )
+	{
+		ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" );
+	}
+	if ( glConfig.smpActive ) {
+		ri.Printf( PRINT_ALL, "Using dual processor acceleration\n" );
+	}
+	if ( r_finish->integer ) {
+		ri.Printf( PRINT_ALL, "Forcing glFinish\n" );
+	}
+}
+
+/*
+================
+GfxMemInfo_f
+================
+*/
+void GfxMemInfo_f( void ) 
+{
+	switch (glRefConfig.memInfo)
+	{
+		case MI_NONE:
+		{
+			ri.Printf(PRINT_ALL, "No extension found for GPU memory info.\n");
+		}
+		break;
+		case MI_NVX:
+		{
+			int value;
+
+			qglGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &value);
+			ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: %ikb\n", value);
+
+			qglGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &value);
+			ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: %ikb\n", value);
+
+			qglGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &value);
+			ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX: %ikb\n", value);
+
+			qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX, &value);
+			ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTION_COUNT_NVX: %i\n", value);
+
+			qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX, &value);
+			ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTED_MEMORY_NVX: %ikb\n", value);
+		}
+		break;
+		case MI_ATI:
+		{
+			// GL_ATI_meminfo
+			int value[4];
+
+			qglGetIntegerv(GL_VBO_FREE_MEMORY_ATI, &value[0]);
+			ri.Printf(PRINT_ALL, "VBO_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]);
+
+			qglGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &value[0]);
+			ri.Printf(PRINT_ALL, "TEXTURE_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]);
+
+			qglGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, &value[0]);
+			ri.Printf(PRINT_ALL, "RENDERBUFFER_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]);
+		}
+		break;
+	}
+}
+
+/*
+===============
+R_Register
+===============
+*/
+void R_Register( void ) 
+{
+	#ifdef USE_RENDERER_DLOPEN
+	com_altivec = ri.Cvar_Get("com_altivec", "1", CVAR_ARCHIVE);
+	#endif	
+
+	//
+	// latched and archived variables
+	//
+	r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH);
+
+	r_ext_draw_range_elements = ri.Cvar_Get( "r_ext_draw_range_elements", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_ext_multi_draw_arrays = ri.Cvar_Get( "r_ext_multi_draw_arrays", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_ext_framebuffer_object = ri.Cvar_Get( "r_ext_framebuffer_object", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_ext_texture_float = ri.Cvar_Get( "r_ext_texture_float", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_arb_half_float_pixel = ri.Cvar_Get( "r_arb_half_float_pixel", "1", CVAR_ARCHIVE | CVAR_LATCH);
+	r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH);
+
+	r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic",
+			"0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH );
+
+	r_picmip = ri.Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH );
+	ri.Cvar_CheckRange( r_picmip, 0, 16, qtrue );
+	r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH );
+	r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	ri.Cvar_CheckRange( r_ext_multisample, 0, 4, qtrue );
+	r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH);
+	r_mode = ri.Cvar_Get( "r_mode", "-2", CVAR_ARCHIVE | CVAR_LATCH );
+	r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE );
+	r_noborder = ri.Cvar_Get("r_noborder", "0", CVAR_ARCHIVE);
+	r_customwidth = ri.Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH );
+	r_customheight = ri.Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH );
+	r_customPixelAspect = ri.Cvar_Get( "r_customPixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0);
+	r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH);
+	r_smp = ri.Cvar_Get( "r_smp", "0", CVAR_ARCHIVE | CVAR_LATCH);
+	r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH);
+	r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH);
+	ri.Cvar_CheckRange(r_greyscale, 0, 1, qfalse);
+
+	r_hdr = ri.Cvar_Get( "r_hdr", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_postProcess = ri.Cvar_Get( "r_postProcess", "1", CVAR_ARCHIVE );
+
+	r_toneMap = ri.Cvar_Get( "r_toneMap", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_forceToneMap = ri.Cvar_Get( "r_forceToneMap", "0", CVAR_CHEAT );
+	r_forceToneMapMin = ri.Cvar_Get( "r_forceToneMapMin", "-3.25", CVAR_CHEAT );
+	r_forceToneMapAvg = ri.Cvar_Get( "r_forceToneMapAvg", "-1.0", CVAR_CHEAT );
+	r_forceToneMapMax = ri.Cvar_Get( "r_forceToneMapMax", "1.0", CVAR_CHEAT );
+
+	r_autoExposure = ri.Cvar_Get( "r_autoExposure", "1", CVAR_ARCHIVE );
+	r_forceAutoExposure = ri.Cvar_Get( "r_forceAutoExposure", "0", CVAR_CHEAT );
+	r_forceAutoExposureMin = ri.Cvar_Get( "r_forceAutoExposureMin", "-2.0", CVAR_CHEAT );
+	r_forceAutoExposureMax = ri.Cvar_Get( "r_forceAutoExposureMax", "2.0", CVAR_CHEAT );
+
+	r_cameraExposure = ri.Cvar_Get( "r_cameraExposure", "0", CVAR_CHEAT );
+
+	r_srgb = ri.Cvar_Get( "r_srgb", "0", CVAR_ARCHIVE | CVAR_LATCH );
+
+	r_depthPrepass = ri.Cvar_Get( "r_depthPrepass", "1", CVAR_ARCHIVE );
+	r_ssao = ri.Cvar_Get( "r_ssao", "0", CVAR_LATCH | CVAR_ARCHIVE );
+
+	r_normalMapping = ri.Cvar_Get( "r_normalMapping", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_specularMapping = ri.Cvar_Get( "r_specularMapping", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_deluxeMapping = ri.Cvar_Get( "r_deluxeMapping", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_parallaxMapping = ri.Cvar_Get( "r_parallaxMapping", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_normalAmbient = ri.Cvar_Get( "r_normalAmbient", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_dlightMode = ri.Cvar_Get( "r_dlightMode", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_pshadowDist = ri.Cvar_Get( "r_pshadowDist", "128", CVAR_ARCHIVE );
+	r_recalcMD3Normals = ri.Cvar_Get( "r_recalcMD3Normals", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_mergeLightmaps = ri.Cvar_Get( "r_mergeLightmaps", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_imageUpsample = ri.Cvar_Get( "r_imageUpsample", "0", CVAR_ARCHIVE | CVAR_LATCH );
+	r_imageUpsampleMaxSize = ri.Cvar_Get( "r_imageUpsampleMaxSize", "1024", CVAR_ARCHIVE | CVAR_LATCH );
+	r_imageUpsampleType = ri.Cvar_Get( "r_imageUpsampleType", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_genNormalMaps = ri.Cvar_Get( "r_genNormalMaps", "0", CVAR_ARCHIVE | CVAR_LATCH );
+
+	r_forceSun = ri.Cvar_Get( "r_forceSun", "0", CVAR_CHEAT );
+	r_forceSunMapLightScale = ri.Cvar_Get( "r_forceSunMapLightScale", "0.5", CVAR_CHEAT );
+	r_forceSunLightScale = ri.Cvar_Get( "r_forceSunLightScale", "0.5", CVAR_CHEAT );
+	r_forceSunAmbientScale = ri.Cvar_Get( "r_forceSunAmbientScale", "0.2", CVAR_CHEAT );
+	r_sunShadows = ri.Cvar_Get( "r_sunShadows", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_shadowFilter = ri.Cvar_Get( "r_shadowFilter", "1", CVAR_ARCHIVE | CVAR_LATCH );
+	r_shadowMapSize = ri.Cvar_Get( "r_shadowMapSize", "1024", CVAR_ARCHIVE | CVAR_LATCH );
+	r_shadowCascadeZNear = ri.Cvar_Get( "r_shadowCascadeZNear", "4", CVAR_ARCHIVE | CVAR_LATCH );
+	r_shadowCascadeZFar = ri.Cvar_Get( "r_shadowCascadeZFar", "3072", CVAR_ARCHIVE | CVAR_LATCH );
+	r_shadowCascadeZBias = ri.Cvar_Get( "r_shadowCascadeZBias", "-320", CVAR_ARCHIVE | CVAR_LATCH );
+
+	//
+	// temporary latched variables that can only change over a restart
+	//
+	r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT );
+	r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH );
+	r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_LATCH );
+	r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH );
+
+	//
+	// archived variables that can change at any time
+	//
+	r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT );
+	r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE );
+	r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE );
+	r_znear = ri.Cvar_Get( "r_znear", "4", CVAR_CHEAT );
+	ri.Cvar_CheckRange( r_znear, 0.001f, 200, qfalse );
+	r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE );
+	r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE );
+	r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE );
+	r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE );
+	r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE );
+	r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE );
+	r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE );
+	r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE );
+	r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE);
+	r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE );
+	r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0",
+					CVAR_ARCHIVE | CVAR_LATCH );
+	r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE );
+	r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE );
+
+	r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE );
+	r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE );
+	r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE );
+
+	r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT );
+	r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT );
+
+	r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE);
+	r_mergeMultidraws = ri.Cvar_Get("r_mergeMultidraws", "1", CVAR_ARCHIVE);
+	r_mergeLeafSurfaces = ri.Cvar_Get("r_mergeLeafSurfaces", "1", CVAR_ARCHIVE);
+
+	//
+	// temporary variables that can change at any time
+	//
+	r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_TEMP );
+
+	r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP );
+	r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT );
+	r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 );
+	r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 );
+
+	r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT );
+	r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT );
+	r_lightmap = ri.Cvar_Get ("r_lightmap", "0", 0 );
+	r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT );
+
+	r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT);
+	r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT);
+	r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT);
+
+	r_showSmp = ri.Cvar_Get ("r_showSmp", "0", CVAR_CHEAT);
+	r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT);
+
+	r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT );
+	r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT );
+	r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT);
+	r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT );
+	r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT );
+	r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT);
+	r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT);
+	r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT);
+	r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT);
+	r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT );
+	r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT );
+	r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT);
+	r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT);
+	r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT);
+	r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT);
+	r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT);
+	r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT);
+	r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT );
+	r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT );
+	r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT );
+	r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT);
+	r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT);
+	r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 );
+
+	r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE);
+
+	r_aviMotionJpegQuality = ri.Cvar_Get("r_aviMotionJpegQuality", "90", CVAR_ARCHIVE);
+	r_screenshotJpegQuality = ri.Cvar_Get("r_screenshotJpegQuality", "90", CVAR_ARCHIVE);
+
+	r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0);
+	r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0);
+
+	// make sure all the commands added here are also
+	// removed in R_Shutdown
+	ri.Cmd_AddCommand( "imagelist", R_ImageList_f );
+	ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f );
+	ri.Cmd_AddCommand( "skinlist", R_SkinList_f );
+	ri.Cmd_AddCommand( "modellist", R_Modellist_f );
+	ri.Cmd_AddCommand( "modelist", R_ModeList_f );
+	ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f );
+	ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f );
+	ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f );
+	ri.Cmd_AddCommand( "minimize", GLimp_Minimize );
+	ri.Cmd_AddCommand( "gfxmeminfo", GfxMemInfo_f );
+}
+
+void R_InitQueries(void)
+{
+	if (!glRefConfig.occlusionQuery)
+		return;
+
+#ifdef REACTION
+	qglGenQueriesARB(ARRAY_SIZE(tr.sunFlareQuery), tr.sunFlareQuery);
+#endif
+}
+
+void R_ShutDownQueries(void)
+{
+	if (!glRefConfig.occlusionQuery)
+		return;
+
+#ifdef REACTION
+	qglDeleteQueriesARB(ARRAY_SIZE(tr.sunFlareQuery), tr.sunFlareQuery);
+#endif
+}
+
+/*
+===============
+R_Init
+===============
+*/
+void R_Init( void ) {	
+	int	err;
+	int i;
+	byte *ptr;
+
+	ri.Printf( PRINT_ALL, "----- R_Init -----\n" );
+
+	// clear all our internal state
+	Com_Memset( &tr, 0, sizeof( tr ) );
+	Com_Memset( &backEnd, 0, sizeof( backEnd ) );
+	Com_Memset( &tess, 0, sizeof( tess ) );
+
+	if(sizeof(glconfig_t) != 11332)
+		ri.Error( ERR_FATAL, "Mod ABI incompatible: sizeof(glconfig_t) == %u != 11332", (unsigned int) sizeof(glconfig_t));
+
+//	Swap_Init();
+
+	if ( (intptr_t)tess.xyz & 15 ) {
+		ri.Printf( PRINT_WARNING, "tess.xyz not 16 byte aligned\n" );
+	}
+	//Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) );
+
+	//
+	// init function tables
+	//
+	for ( i = 0; i < FUNCTABLE_SIZE; i++ )
+	{
+		tr.sinTable[i]		= sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) );
+		tr.squareTable[i]	= ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f;
+		tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE;
+		tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i];
+
+		if ( i < FUNCTABLE_SIZE / 2 )
+		{
+			if ( i < FUNCTABLE_SIZE / 4 )
+			{
+				tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 );
+			}
+			else
+			{
+				tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4];
+			}
+		}
+		else
+		{
+			tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2];
+		}
+	}
+
+	R_InitFogTable();
+
+	R_NoiseInit();
+
+	R_Register();
+
+	max_polys = r_maxpolys->integer;
+	if (max_polys < MAX_POLYS)
+		max_polys = MAX_POLYS;
+
+	max_polyverts = r_maxpolyverts->integer;
+	if (max_polyverts < MAX_POLYVERTS)
+		max_polyverts = MAX_POLYVERTS;
+
+	ptr = ri.Hunk_Alloc( sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low);
+	backEndData[0] = (backEndData_t *) ptr;
+	backEndData[0]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[0] ));
+	backEndData[0]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys);
+	if ( r_smp->integer ) {
+		ptr = ri.Hunk_Alloc( sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low);
+		backEndData[1] = (backEndData_t *) ptr;
+		backEndData[1]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[1] ));
+		backEndData[1]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys);
+	} else {
+		backEndData[1] = NULL;
+	}
+	R_ToggleSmpFrame();
+
+	InitOpenGL();
+
+	R_InitImages();
+
+	FBO_Init();
+
+	GLSL_InitGPUShaders();
+
+	R_InitVBOs();
+
+	R_InitShaders();
+
+	R_InitSkins();
+
+	R_ModelInit();
+
+	R_InitFreeType();
+
+	R_InitQueries();
+
+
+	err = qglGetError();
+	if ( err != GL_NO_ERROR )
+		ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err);
+
+	// print info
+	GfxInfo_f();
+	ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" );
+}
+
+/*
+===============
+RE_Shutdown
+===============
+*/
+void RE_Shutdown( qboolean destroyWindow ) {	
+
+	ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow );
+
+	ri.Cmd_RemoveCommand ("modellist");
+	ri.Cmd_RemoveCommand ("screenshotJPEG");
+	ri.Cmd_RemoveCommand ("screenshot");
+	ri.Cmd_RemoveCommand ("imagelist");
+	ri.Cmd_RemoveCommand ("shaderlist");
+	ri.Cmd_RemoveCommand ("skinlist");
+	ri.Cmd_RemoveCommand ("gfxinfo");
+	ri.Cmd_RemoveCommand("minimize");
+	ri.Cmd_RemoveCommand( "modelist" );
+	ri.Cmd_RemoveCommand( "shaderstate" );
+
+
+	if ( tr.registered ) {
+		R_SyncRenderThread();
+		R_ShutdownCommandBuffers();
+		R_ShutDownQueries();
+		FBO_Shutdown();
+		R_DeleteTextures();
+		R_ShutdownVBOs();
+		GLSL_ShutdownGPUShaders();
+	}
+
+	R_DoneFreeType();
+
+	// shut down platform specific OpenGL stuff
+	if ( destroyWindow ) {
+		GLimp_Shutdown();
+	}
+
+	tr.registered = qfalse;
+}
+
+
+/*
+=============
+RE_EndRegistration
+
+Touch all images to make sure they are resident
+=============
+*/
+void RE_EndRegistration( void ) {
+	R_SyncRenderThread();
+	if (!ri.Sys_LowPhysicalMemory()) {
+		RB_ShowImages();
+	}
+}
+
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+GetRefAPI
+
+@@@@@@@@@@@@@@@@@@@@@
+*/
+#ifdef USE_RENDERER_DLOPEN
+Q_EXPORT refexport_t QDECL *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
+#else
+refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
+#endif
+
+	static refexport_t	re;
+
+	ri = *rimp;
+
+	Com_Memset( &re, 0, sizeof( re ) );
+
+	if ( apiVersion != REF_API_VERSION ) {
+		ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", 
+			REF_API_VERSION, apiVersion );
+		return NULL;
+	}
+
+	// the RE_ functions are Renderer Entry points
+
+	re.Shutdown = RE_Shutdown;
+
+	re.BeginRegistration = RE_BeginRegistration;
+	re.RegisterModel = RE_RegisterModel;
+	re.RegisterSkin = RE_RegisterSkin;
+	re.RegisterShader = RE_RegisterShader;
+	re.RegisterShaderNoMip = RE_RegisterShaderNoMip;
+	re.LoadWorld = RE_LoadWorldMap;
+	re.SetWorldVisData = RE_SetWorldVisData;
+	re.EndRegistration = RE_EndRegistration;
+
+	re.BeginFrame = RE_BeginFrame;
+	re.EndFrame = RE_EndFrame;
+
+	re.MarkFragments = R_MarkFragments;
+	re.LerpTag = R_LerpTag;
+	re.ModelBounds = R_ModelBounds;
+
+	re.ClearScene = RE_ClearScene;
+	re.AddRefEntityToScene = RE_AddRefEntityToScene;
+	re.AddPolyToScene = RE_AddPolyToScene;
+	re.LightForPoint = R_LightForPoint;
+	re.AddLightToScene = RE_AddLightToScene;
+	re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene;
+	re.RenderScene = RE_RenderScene;
+
+	re.SetColor = RE_SetColor;
+	re.DrawStretchPic = RE_StretchPic;
+	re.DrawStretchRaw = RE_StretchRaw;
+	re.UploadCinematic = RE_UploadCinematic;
+
+	re.RegisterFont = RE_RegisterFont;
+	re.RemapShader = R_RemapShader;
+	re.GetEntityToken = R_GetEntityToken;
+	re.inPVS = R_inPVS;
+
+	re.TakeVideoFrame = RE_TakeVideoFrame;
+
+	return &re;
+}

Added: trunk/code/rend2/tr_light.c
===================================================================
--- trunk/code/rend2/tr_light.c	                        (rev 0)
+++ trunk/code/rend2/tr_light.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,455 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_light.c
+
+#include "tr_local.h"
+
+#define	DLIGHT_AT_RADIUS		16
+// at the edge of a dlight's influence, this amount of light will be added
+
+#define	DLIGHT_MINIMUM_RADIUS	16		
+// never calculate a range less than this to prevent huge light numbers
+
+
+/*
+===============
+R_TransformDlights
+
+Transforms the origins of an array of dlights.
+Used by both the front end (for DlightBmodel) and
+the back end (before doing the lighting calculation)
+===============
+*/
+void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or) {
+	int		i;
+	vec3_t	temp;
+
+	for ( i = 0 ; i < count ; i++, dl++ ) {
+		VectorSubtract( dl->origin, or->origin, temp );
+		dl->transformed[0] = DotProduct( temp, or->axis[0] );
+		dl->transformed[1] = DotProduct( temp, or->axis[1] );
+		dl->transformed[2] = DotProduct( temp, or->axis[2] );
+	}
+}
+
+/*
+=============
+R_DlightBmodel
+
+Determine which dynamic lights may effect this bmodel
+=============
+*/
+void R_DlightBmodel( bmodel_t *bmodel ) {
+	int			i, j;
+	dlight_t	*dl;
+	int			mask;
+	msurface_t	*surf;
+
+	// transform all the lights
+	R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or );
+
+	mask = 0;
+	for ( i=0 ; i<tr.refdef.num_dlights ; i++ ) {
+		dl = &tr.refdef.dlights[i];
+
+		// see if the point is close enough to the bounds to matter
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( dl->transformed[j] - bmodel->bounds[1][j] > dl->radius ) {
+				break;
+			}
+			if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) {
+				break;
+			}
+		}
+		if ( j < 3 ) {
+			continue;
+		}
+
+		// we need to check this light
+		mask |= 1 << i;
+	}
+
+	tr.currentEntity->needDlights = (mask != 0);
+
+	// set the dlight bits in all the surfaces
+	for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) {
+		surf = tr.world->surfaces + bmodel->firstSurface + i;
+
+		if ( *surf->data == SF_FACE ) {
+			((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+		} else if ( *surf->data == SF_GRID ) {
+			((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+		} else if ( *surf->data == SF_TRIANGLES ) {
+			((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+		}
+	}
+}
+
+
+/*
+=============================================================================
+
+LIGHT SAMPLING
+
+=============================================================================
+*/
+
+extern	cvar_t	*r_ambientScale;
+extern	cvar_t	*r_directedScale;
+extern	cvar_t	*r_debugLight;
+
+/*
+=================
+R_SetupEntityLightingGrid
+
+=================
+*/
+static void R_SetupEntityLightingGrid( trRefEntity_t *ent, world_t *world ) {
+	vec3_t	lightOrigin;
+	int		pos[3];
+	int		i, j;
+	byte	*gridData;
+	float	frac[3];
+	int		gridStep[3];
+	vec3_t	direction;
+	float	totalFactor;
+
+	if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) {
+		// seperate lightOrigins are needed so an object that is
+		// sinking into the ground can still be lit, and so
+		// multi-part models can be lit identically
+		VectorCopy( ent->e.lightingOrigin, lightOrigin );
+	} else {
+		VectorCopy( ent->e.origin, lightOrigin );
+	}
+
+	VectorSubtract( lightOrigin, world->lightGridOrigin, lightOrigin );
+	for ( i = 0 ; i < 3 ; i++ ) {
+		float	v;
+
+		v = lightOrigin[i]*world->lightGridInverseSize[i];
+		pos[i] = floor( v );
+		frac[i] = v - pos[i];
+		if ( pos[i] < 0 ) {
+			pos[i] = 0;
+		} else if ( pos[i] >= world->lightGridBounds[i] - 1 ) {
+			pos[i] = world->lightGridBounds[i] - 1;
+		}
+	}
+
+	VectorClear( ent->ambientLight );
+	VectorClear( ent->directedLight );
+	VectorClear( direction );
+
+	assert( world->lightGridData ); // NULL with -nolight maps
+
+	// trilerp the light value
+	gridStep[0] = 8;
+	gridStep[1] = 8 * world->lightGridBounds[0];
+	gridStep[2] = 8 * world->lightGridBounds[0] * world->lightGridBounds[1];
+	gridData = world->lightGridData + pos[0] * gridStep[0]
+		+ pos[1] * gridStep[1] + pos[2] * gridStep[2];
+
+	totalFactor = 0;
+	for ( i = 0 ; i < 8 ; i++ ) {
+		float	factor;
+		byte	*data;
+		int		lat, lng;
+		vec3_t	normal;
+		qboolean ignore;
+		#if idppc
+		float d0, d1, d2, d3, d4, d5;
+		#endif
+		factor = 1.0;
+		data = gridData;
+		ignore = qfalse;
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( i & (1<<j) ) {
+				if ((pos[j] + 1) >= world->lightGridBounds[j] - 1)
+				{
+					ignore = qtrue; // ignore values outside lightgrid
+				}
+				factor *= frac[j];
+				data += gridStep[j];
+			} else {
+				factor *= (1.0f - frac[j]);
+			}
+		}
+
+		if ( ignore )
+			continue;
+
+		if (world->hdrLightGrid)
+		{
+			float *hdrData = world->hdrLightGrid + (int)(data - world->lightGridData) / 8 * 6;
+			if (!(hdrData[0]+hdrData[1]+hdrData[2]+hdrData[3]+hdrData[4]+hdrData[5]) ) {
+				continue;	// ignore samples in walls
+			}
+		}
+		else
+		{
+			if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) ) {
+				continue;	// ignore samples in walls
+			}
+		}
+		totalFactor += factor;
+		#if idppc
+		d0 = data[0]; d1 = data[1]; d2 = data[2];
+		d3 = data[3]; d4 = data[4]; d5 = data[5];
+
+		ent->ambientLight[0] += factor * d0;
+		ent->ambientLight[1] += factor * d1;
+		ent->ambientLight[2] += factor * d2;
+
+		ent->directedLight[0] += factor * d3;
+		ent->directedLight[1] += factor * d4;
+		ent->directedLight[2] += factor * d5;
+		#else
+		if (world->hdrLightGrid)
+		{
+			// FIXME: this is hideous
+			float *hdrData = world->hdrLightGrid + (int)(data - world->lightGridData) / 8 * 6;
+
+			ent->ambientLight[0] += factor * hdrData[0];
+			ent->ambientLight[1] += factor * hdrData[1];
+			ent->ambientLight[2] += factor * hdrData[2];
+
+			ent->directedLight[0] += factor * hdrData[3];
+			ent->directedLight[1] += factor * hdrData[4];
+			ent->directedLight[2] += factor * hdrData[5];
+		}
+		else
+		{
+			ent->ambientLight[0] += factor * data[0];
+			ent->ambientLight[1] += factor * data[1];
+			ent->ambientLight[2] += factor * data[2];
+
+			ent->directedLight[0] += factor * data[3];
+			ent->directedLight[1] += factor * data[4];
+			ent->directedLight[2] += factor * data[5];
+		}
+		#endif
+		lat = data[7];
+		lng = data[6];
+		lat *= (FUNCTABLE_SIZE/256);
+		lng *= (FUNCTABLE_SIZE/256);
+
+		// decode X as cos( lat ) * sin( long )
+		// decode Y as sin( lat ) * sin( long )
+		// decode Z as cos( long )
+
+		normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+		normal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+		normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+		VectorMA( direction, factor, normal, direction );
+	}
+
+	if ( totalFactor > 0 && totalFactor < 0.99 ) {
+		totalFactor = 1.0f / totalFactor;
+		VectorScale( ent->ambientLight, totalFactor, ent->ambientLight );
+		VectorScale( ent->directedLight, totalFactor, ent->directedLight );
+	}
+
+	VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight );
+	VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight );
+
+	VectorNormalize2( direction, ent->lightDir );
+}
+
+
+/*
+===============
+LogLight
+===============
+*/
+static void LogLight( trRefEntity_t *ent ) {
+	int	max1, max2;
+
+	if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) {
+		return;
+	}
+
+	max1 = ent->ambientLight[0];
+	if ( ent->ambientLight[1] > max1 ) {
+		max1 = ent->ambientLight[1];
+	} else if ( ent->ambientLight[2] > max1 ) {
+		max1 = ent->ambientLight[2];
+	}
+
+	max2 = ent->directedLight[0];
+	if ( ent->directedLight[1] > max2 ) {
+		max2 = ent->directedLight[1];
+	} else if ( ent->directedLight[2] > max2 ) {
+		max2 = ent->directedLight[2];
+	}
+
+	ri.Printf( PRINT_ALL, "amb:%i  dir:%i\n", max1, max2 );
+}
+
+/*
+=================
+R_SetupEntityLighting
+
+Calculates all the lighting values that will be used
+by the Calc_* functions
+=================
+*/
+void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) {
+	int				i;
+	dlight_t		*dl;
+	float			power;
+	vec3_t			dir;
+	float			d;
+	vec3_t			lightDir;
+	vec3_t			lightOrigin;
+
+	// lighting calculations 
+	if ( ent->lightingCalculated ) {
+		return;
+	}
+	ent->lightingCalculated = qtrue;
+
+	//
+	// trace a sample point down to find ambient light
+	//
+	if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) {
+		// seperate lightOrigins are needed so an object that is
+		// sinking into the ground can still be lit, and so
+		// multi-part models can be lit identically
+		VectorCopy( ent->e.lightingOrigin, lightOrigin );
+	} else {
+		VectorCopy( ent->e.origin, lightOrigin );
+	}
+
+	// if NOWORLDMODEL, only use dynamic lights (menu system, etc)
+	if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) 
+		&& tr.world->lightGridData ) {
+		R_SetupEntityLightingGrid( ent, tr.world );
+	} else {
+		ent->ambientLight[0] = ent->ambientLight[1] = 
+			ent->ambientLight[2] = tr.identityLight * 150;
+		ent->directedLight[0] = ent->directedLight[1] = 
+			ent->directedLight[2] = tr.identityLight * 150;
+		VectorCopy( tr.sunDirection, ent->lightDir );
+	}
+
+	// bonus items and view weapons have a fixed minimum add
+	if ( !r_hdr->integer /* ent->e.renderfx & RF_MINLIGHT */ ) {
+		// give everything a minimum light add
+		ent->ambientLight[0] += tr.identityLight * 32;
+		ent->ambientLight[1] += tr.identityLight * 32;
+		ent->ambientLight[2] += tr.identityLight * 32;
+	}
+
+	//
+	// modify the light by dynamic lights
+	//
+	d = VectorLength( ent->directedLight );
+	VectorScale( ent->lightDir, d, lightDir );
+
+	for ( i = 0 ; i < refdef->num_dlights ; i++ ) {
+		dl = &refdef->dlights[i];
+		VectorSubtract( dl->origin, lightOrigin, dir );
+		d = VectorNormalize( dir );
+
+		power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius );
+		if ( d < DLIGHT_MINIMUM_RADIUS ) {
+			d = DLIGHT_MINIMUM_RADIUS;
+		}
+		d = power / ( d * d );
+
+		VectorMA( ent->directedLight, d, dl->color, ent->directedLight );
+		VectorMA( lightDir, d, dir, lightDir );
+	}
+
+	// clamp ambient
+	if ( !r_hdr->integer )
+	{
+		for ( i = 0 ; i < 3 ; i++ ) {
+			if ( ent->ambientLight[i] > tr.identityLightByte ) {
+				ent->ambientLight[i] = tr.identityLightByte;
+			}
+		}
+	}
+
+	if ( r_debugLight->integer ) {
+		LogLight( ent );
+	}
+
+	// save out the byte packet version
+	((byte *)&ent->ambientLightInt)[0] = ri.ftol(ent->ambientLight[0]);
+	((byte *)&ent->ambientLightInt)[1] = ri.ftol(ent->ambientLight[1]);
+	((byte *)&ent->ambientLightInt)[2] = ri.ftol(ent->ambientLight[2]);
+	((byte *)&ent->ambientLightInt)[3] = 0xff;
+	
+	// transform the direction to local space
+	// no need to do this if using lightentity glsl shader
+	VectorNormalize( lightDir );
+	VectorCopy(lightDir, ent->lightDir);
+}
+
+/*
+=================
+R_LightForPoint
+=================
+*/
+int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir )
+{
+	trRefEntity_t ent;
+	
+	if ( tr.world->lightGridData == NULL )
+	  return qfalse;
+
+	Com_Memset(&ent, 0, sizeof(ent));
+	VectorCopy( point, ent.e.origin );
+	R_SetupEntityLightingGrid( &ent, tr.world );
+	VectorCopy(ent.ambientLight, ambientLight);
+	VectorCopy(ent.directedLight, directedLight);
+	VectorCopy(ent.lightDir, lightDir);
+
+	return qtrue;
+}
+
+
+int R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world )
+{
+	trRefEntity_t ent;
+	
+	if ( world->lightGridData == NULL )
+	  return qfalse;
+
+	Com_Memset(&ent, 0, sizeof(ent));
+	VectorCopy( point, ent.e.origin );
+	R_SetupEntityLightingGrid( &ent, world );
+
+	if ((DotProduct(ent.lightDir, ent.lightDir) < 0.9f) || (DotProduct(ent.lightDir, normal) < 0.1f))
+	{
+		VectorCopy(normal, lightDir);
+	}
+	else
+	{
+		VectorCopy(ent.lightDir, lightDir);
+	}
+
+	return qtrue;
+}
\ No newline at end of file

Added: trunk/code/rend2/tr_local.h
===================================================================
--- trunk/code/rend2/tr_local.h	                        (rev 0)
+++ trunk/code/rend2/tr_local.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,2856 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+
+#ifndef TR_LOCAL_H
+#define TR_LOCAL_H
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "../renderer/tr_public.h"
+#include "tr_extratypes.h"
+#include "tr_extramath.h"
+#include "tr_fbo.h"
+#include "tr_postprocess.h"
+#include "qgl.h"
+#include "../renderer/iqm.h"
+
+#define GL_INDEX_TYPE		GL_UNSIGNED_INT
+typedef unsigned int glIndex_t;
+
+#define BUFFER_OFFSET(i) ((char *)NULL + (i))
+
+#ifndef ARRAY_SIZE
+#	define ARRAY_SIZE(arr)                          (sizeof(arr) / sizeof(arr[0]))
+#endif
+
+// everything that is needed by the backend needs
+// to be double buffered to allow it to run in
+// parallel on a dual cpu machine
+#define	SMP_FRAMES		2
+
+// 14 bits
+// can't be increased without changing bit packing for drawsurfs
+// see QSORT_SHADERNUM_SHIFT
+#define SHADERNUM_BITS	14
+#define MAX_SHADERS		(1<<SHADERNUM_BITS)
+
+//#define MAX_SHADER_STATES 2048
+#define MAX_STATES_PER_SHADER 32
+#define MAX_STATE_NAME 32
+
+#define	MAX_FBOS      64
+#define MAX_VISCOUNTS 5
+#define MAX_VBOS      4096
+#define MAX_IBOS      4096
+
+#define MAX_CALC_PSHADOWS    64
+#define MAX_DRAWN_PSHADOWS    16 // do not increase past 32, because bit flags are used on surfaces
+#define PSHADOW_MAP_SIZE      512
+
+#define USE_VERT_TANGENT_SPACE
+
+typedef struct dlight_s {
+	vec3_t	origin;
+	vec3_t	color;				// range from 0.0 to 1.0, should be color normalized
+	float	radius;
+
+	vec3_t	transformed;		// origin in local coordinate system
+	int		additive;			// texture detail is lost tho when the lightmap is dark
+} dlight_t;
+
+
+// a trRefEntity_t has all the information passed in by
+// the client game, as well as some locally derived info
+typedef struct {
+	refEntity_t	e;
+
+	float		axisLength;		// compensate for non-normalized axis
+
+	qboolean	needDlights;	// true for bmodels that touch a dlight
+	qboolean	lightingCalculated;
+#ifdef REACTION
+	// JBravo: Mirrored models
+	qboolean	mirrored;		// mirrored matrix, needs reversed culling
+#endif
+	vec3_t		lightDir;		// normalized direction towards light
+	vec3_t		ambientLight;	// color normalized to 0-255
+	int			ambientLightInt;	// 32 bit rgba packed
+	vec3_t		directedLight;
+} trRefEntity_t;
+
+
+typedef struct {
+	vec3_t		origin;			// in world coordinates
+	vec3_t		axis[3];		// orientation in world
+	vec3_t		viewOrigin;		// viewParms->or.origin in local coordinates
+	float		modelMatrix[16];
+	float		transformMatrix[16];
+} orientationr_t;
+
+typedef enum
+{
+	IMGTYPE_COLORALPHA, // for color, lightmap, diffuse, and specular
+	IMGTYPE_NORMAL,
+	IMGTYPE_NORMALHEIGHT,
+	IMGTYPE_DELUXE, // normals are swizzled, deluxe are not
+} imgType_t;
+
+typedef enum
+{
+	IMGFLAG_NONE           = 0x0000,
+	IMGFLAG_MIPMAP         = 0x0001,
+	IMGFLAG_PICMIP         = 0x0002,
+	IMGFLAG_CUBEMAP        = 0x0004,
+	IMGFLAG_NO_COMPRESSION = 0x0010,
+	IMGFLAG_NOLIGHTSCALE   = 0x0020,
+	IMGFLAG_CLAMPTOEDGE    = 0x0040,
+	IMGFLAG_SRGB           = 0x0080,
+	IMGFLAG_GENNORMALMAP   = 0x0100,
+} imgFlags_t;
+
+typedef struct image_s {
+	char		imgName[MAX_QPATH];		// game path, including extension
+	int			width, height;				// source image
+	int			uploadWidth, uploadHeight;	// after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE
+	GLuint		texnum;					// gl texture binding
+
+	int			frameUsed;			// for texture usage in frame statistics
+
+	int			internalFormat;
+	int			TMU;				// only needed for voodoo2
+
+	imgType_t   type;
+	imgFlags_t  flags;
+
+	struct image_s*	next;
+} image_t;
+
+typedef enum
+{
+	VBO_USAGE_STATIC,
+	VBO_USAGE_DYNAMIC
+} vboUsage_t;
+
+typedef struct VBO_s
+{
+	char            name[MAX_QPATH];
+
+	uint32_t        vertexesVBO;
+	int             vertexesSize;	// amount of memory data allocated for all vertices in bytes
+	uint32_t        ofs_xyz;
+	uint32_t        ofs_normal;
+	uint32_t        ofs_st;
+	uint32_t        ofs_lightmap;
+	uint32_t        ofs_vertexcolor;
+	uint32_t        ofs_lightdir;
+#ifdef USE_VERT_TANGENT_SPACE
+	uint32_t        ofs_tangent;
+	uint32_t        ofs_bitangent;
+#endif
+	uint32_t        stride_xyz;
+	uint32_t        stride_normal;
+	uint32_t        stride_st;
+	uint32_t        stride_lightmap;
+	uint32_t        stride_vertexcolor;
+	uint32_t        stride_lightdir;
+#ifdef USE_VERT_TANGENT_SPACE
+	uint32_t        stride_tangent;
+	uint32_t        stride_bitangent;
+#endif
+	uint32_t        size_xyz;
+	uint32_t        size_normal;
+
+	int             attribs;
+} VBO_t;
+
+typedef struct IBO_s
+{
+	char            name[MAX_QPATH];
+
+	uint32_t        indexesVBO;
+	int             indexesSize;	// amount of memory data allocated for all triangles in bytes
+//  uint32_t        ofsIndexes;
+} IBO_t;
+
+//===============================================================================
+
+typedef enum {
+	SS_BAD,
+	SS_PORTAL,			// mirrors, portals, viewscreens
+	SS_ENVIRONMENT,		// sky box
+	SS_OPAQUE,			// opaque
+
+	SS_DECAL,			// scorch marks, etc.
+	SS_SEE_THROUGH,		// ladders, grates, grills that may have small blended edges
+						// in addition to alpha test
+	SS_BANNER,
+
+	SS_FOG,
+
+	SS_UNDERWATER,		// for items that should be drawn in front of the water plane
+
+	SS_BLEND0,			// regular transparency and filters
+	SS_BLEND1,			// generally only used for additive type effects
+	SS_BLEND2,
+	SS_BLEND3,
+
+	SS_BLEND6,
+	SS_STENCIL_SHADOW,
+	SS_ALMOST_NEAREST,	// gun smoke puffs
+
+	SS_NEAREST			// blood blobs
+} shaderSort_t;
+
+
+#define MAX_SHADER_STAGES 8
+
+typedef enum {
+	GF_NONE,
+
+	GF_SIN,
+	GF_SQUARE,
+	GF_TRIANGLE,
+	GF_SAWTOOTH, 
+	GF_INVERSE_SAWTOOTH, 
+
+	GF_NOISE
+
+} genFunc_t;
+
+
+typedef enum {
+	DEFORM_NONE,
+	DEFORM_WAVE,
+	DEFORM_NORMALS,
+	DEFORM_BULGE,
+	DEFORM_MOVE,
+	DEFORM_PROJECTION_SHADOW,
+	DEFORM_AUTOSPRITE,
+	DEFORM_AUTOSPRITE2,
+	DEFORM_TEXT0,
+	DEFORM_TEXT1,
+	DEFORM_TEXT2,
+	DEFORM_TEXT3,
+	DEFORM_TEXT4,
+	DEFORM_TEXT5,
+	DEFORM_TEXT6,
+	DEFORM_TEXT7
+} deform_t;
+
+// deformVertexes types that can be handled by the GPU
+typedef enum
+{
+	// do not edit: same as genFunc_t
+
+	DGEN_NONE,
+	DGEN_WAVE_SIN,
+	DGEN_WAVE_SQUARE,
+	DGEN_WAVE_TRIANGLE,
+	DGEN_WAVE_SAWTOOTH,
+	DGEN_WAVE_INVERSE_SAWTOOTH,
+	DGEN_WAVE_NOISE,
+
+	// do not edit until this line
+
+	DGEN_BULGE,
+	DGEN_MOVE
+} deformGen_t;
+
+typedef enum {
+	AGEN_IDENTITY,
+	AGEN_SKIP,
+	AGEN_ENTITY,
+	AGEN_ONE_MINUS_ENTITY,
+	AGEN_VERTEX,
+	AGEN_ONE_MINUS_VERTEX,
+	AGEN_LIGHTING_SPECULAR,
+	AGEN_WAVEFORM,
+	AGEN_PORTAL,
+	AGEN_CONST,
+	AGEN_FRESNEL
+} alphaGen_t;
+
+typedef enum {
+	CGEN_BAD,
+	CGEN_IDENTITY_LIGHTING,	// tr.identityLight
+	CGEN_IDENTITY,			// always (1,1,1,1)
+	CGEN_ENTITY,			// grabbed from entity's modulate field
+	CGEN_ONE_MINUS_ENTITY,	// grabbed from 1 - entity.modulate
+	CGEN_EXACT_VERTEX,		// tess.vertexColors
+	CGEN_VERTEX,			// tess.vertexColors * tr.identityLight
+	CGEN_EXACT_VERTEX_LIT,	// like CGEN_EXACT_VERTEX but takes a light direction from the lightgrid
+	CGEN_VERTEX_LIT,		// like CGEN_VERTEX but takes a light direction from the lightgrid
+	CGEN_ONE_MINUS_VERTEX,
+	CGEN_WAVEFORM,			// programmatically generated
+	CGEN_LIGHTING_DIFFUSE,
+	CGEN_FOG,				// standard fog
+	CGEN_CONST				// fixed color
+} colorGen_t;
+
+typedef enum {
+	TCGEN_BAD,
+	TCGEN_IDENTITY,			// clear to 0,0
+	TCGEN_LIGHTMAP,
+	TCGEN_TEXTURE,
+	TCGEN_ENVIRONMENT_MAPPED,
+	TCGEN_FOG,
+	TCGEN_VECTOR			// S and T from world coordinates
+} texCoordGen_t;
+
+typedef enum {
+	ACFF_NONE,
+	ACFF_MODULATE_RGB,
+	ACFF_MODULATE_RGBA,
+	ACFF_MODULATE_ALPHA
+} acff_t;
+
+typedef struct {
+	genFunc_t	func;
+
+	float base;
+	float amplitude;
+	float phase;
+	float frequency;
+} waveForm_t;
+
+#define TR_MAX_TEXMODS 4
+
+typedef enum {
+	TMOD_NONE,
+	TMOD_TRANSFORM,
+	TMOD_TURBULENT,
+	TMOD_SCROLL,
+	TMOD_SCALE,
+	TMOD_STRETCH,
+	TMOD_ROTATE,
+	TMOD_ENTITY_TRANSLATE
+} texMod_t;
+
+#define	MAX_SHADER_DEFORMS	3
+typedef struct {
+	deform_t	deformation;			// vertex coordinate modification type
+
+	vec3_t		moveVector;
+	waveForm_t	deformationWave;
+	float		deformationSpread;
+
+	float		bulgeWidth;
+	float		bulgeHeight;
+	float		bulgeSpeed;
+} deformStage_t;
+
+
+typedef struct {
+	texMod_t		type;
+
+	// used for TMOD_TURBULENT and TMOD_STRETCH
+	waveForm_t		wave;
+
+	// used for TMOD_TRANSFORM
+	float			matrix[2][2];		// s' = s * m[0][0] + t * m[1][0] + trans[0]
+	float			translate[2];		// t' = s * m[0][1] + t * m[0][1] + trans[1]
+
+	// used for TMOD_SCALE
+	float			scale[2];			// s *= scale[0]
+	                                    // t *= scale[1]
+
+	// used for TMOD_SCROLL
+	float			scroll[2];			// s' = s + scroll[0] * time
+										// t' = t + scroll[1] * time
+
+	// + = clockwise
+	// - = counterclockwise
+	float			rotateSpeed;
+
+} texModInfo_t;
+
+
+#define	MAX_IMAGE_ANIMATIONS	8
+
+typedef struct {
+	image_t			*image[MAX_IMAGE_ANIMATIONS];
+	int				numImageAnimations;
+	float			imageAnimationSpeed;
+
+	texCoordGen_t	tcGen;
+	vec3_t			tcGenVectors[2];
+
+	int				numTexMods;
+	texModInfo_t	*texMods;
+
+	int				videoMapHandle;
+	qboolean		isLightmap;
+	qboolean		vertexLightmap;
+	qboolean		isVideoMap;
+} textureBundle_t;
+
+enum
+{
+	TB_COLORMAP    = 0,
+	TB_DIFFUSEMAP  = 0,
+	TB_LIGHTMAP    = 1,
+	TB_LEVELSMAP   = 1,
+	TB_SHADOWMAP   = 1,
+	TB_NORMALMAP   = 2,
+	TB_DELUXEMAP   = 3,
+	TB_SHADOWMAP2  = 3,
+	TB_SPECULARMAP = 4,
+	TB_SHADOWMAP3  = 5,
+	NUM_TEXTURE_BUNDLES = 6
+};
+
+typedef enum
+{
+	// material shader stage types
+	ST_COLORMAP = 0,			// vanilla Q3A style shader treatening
+	ST_DIFFUSEMAP = 0,          // treat color and diffusemap the same
+	ST_NORMALMAP,
+	ST_NORMALPARALLAXMAP,
+	ST_SPECULARMAP,
+	ST_GLSL
+} stageType_t;
+
+typedef struct {
+	qboolean		active;
+	
+	textureBundle_t	bundle[NUM_TEXTURE_BUNDLES];
+
+	waveForm_t		rgbWave;
+	colorGen_t		rgbGen;
+
+	waveForm_t		alphaWave;
+	alphaGen_t		alphaGen;
+
+	byte			constantColor[4];			// for CGEN_CONST and AGEN_CONST
+
+	unsigned		stateBits;					// GLS_xxxx mask
+
+	acff_t			adjustColorsForFog;
+
+	qboolean		isDetail;
+
+	stageType_t     type;
+	struct shaderProgram_s *glslShaderGroup;
+	int glslShaderIndex;
+	vec2_t materialInfo;
+} shaderStage_t;
+
+struct shaderCommands_s;
+
+// any change in the LIGHTMAP_* defines here MUST be reflected in
+// R_FindShader() in tr_bsp.c
+#define LIGHTMAP_2D         -4	// shader is for 2D rendering
+#define LIGHTMAP_BY_VERTEX  -3	// pre-lit triangle models
+#define LIGHTMAP_WHITEIMAGE -2
+#define LIGHTMAP_NONE       -1
+
+typedef enum {
+	CT_FRONT_SIDED,
+	CT_BACK_SIDED,
+	CT_TWO_SIDED
+} cullType_t;
+
+typedef enum {
+	FP_NONE,		// surface is translucent and will just be adjusted properly
+	FP_EQUAL,		// surface is opaque but possibly alpha tested
+	FP_LE			// surface is trnaslucent, but still needs a fog pass (fog surface)
+} fogPass_t;
+
+typedef struct {
+	float		cloudHeight;
+	image_t		*outerbox[6], *innerbox[6];
+} skyParms_t;
+
+typedef struct {
+	vec3_t	color;
+	float	depthForOpaque;
+} fogParms_t;
+
+
+typedef struct shader_s {
+	char		name[MAX_QPATH];		// game path, including extension
+	int			lightmapIndex;			// for a shader to match, both name and lightmapIndex must match
+
+	int			index;					// this shader == tr.shaders[index]
+	int			sortedIndex;			// this shader == tr.sortedShaders[sortedIndex]
+
+	float		sort;					// lower numbered shaders draw before higher numbered
+
+	qboolean	defaultShader;			// we want to return index 0 if the shader failed to
+										// load for some reason, but R_FindShader should
+										// still keep a name allocated for it, so if
+										// something calls RE_RegisterShader again with
+										// the same name, we don't try looking for it again
+
+	qboolean	explicitlyDefined;		// found in a .shader file
+
+	int			surfaceFlags;			// if explicitlyDefined, this will have SURF_* flags
+	int			contentFlags;
+
+	qboolean	entityMergable;			// merge across entites optimizable (smoke, blood)
+
+	qboolean	isSky;
+	skyParms_t	sky;
+	fogParms_t	fogParms;
+
+	float		portalRange;			// distance to fog out at
+	qboolean	isPortal;
+
+	int			multitextureEnv;		// 0, GL_MODULATE, GL_ADD (FIXME: put in stage)
+
+	cullType_t	cullType;				// CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED
+	qboolean	polygonOffset;			// set for decals and other items that must be offset 
+	qboolean	noMipMaps;				// for console fonts, 2D elements, etc.
+	qboolean	noPicMip;				// for images that must always be full resolution
+
+	fogPass_t	fogPass;				// draw a blended pass, possibly with depth test equals
+
+	int         vertexAttribs;          // not all shaders will need all data to be gathered
+
+	int			numDeforms;
+	deformStage_t	deforms[MAX_SHADER_DEFORMS];
+
+	int			numUnfoggedPasses;
+	shaderStage_t	*stages[MAX_SHADER_STAGES];		
+
+	void		(*optimalStageIteratorFunc)( void );
+
+  float clampTime;                                  // time this shader is clamped to
+  float timeOffset;                                 // current time offset for this shader
+
+  int numStates;                                    // if non-zero this is a state shader
+  struct shader_s *currentShader;                   // current state if this is a state shader
+  struct shader_s *parentShader;                    // current state if this is a state shader
+  int currentState;                                 // current state index for cycle purposes
+  long expireTime;                                  // time in milliseconds this expires
+
+  struct shader_s *remappedShader;                  // current shader this one is remapped too
+
+  int shaderStates[MAX_STATES_PER_SHADER];          // index to valid shader states
+
+	struct	shader_s	*next;
+} shader_t;
+
+static ID_INLINE qboolean ShaderRequiresCPUDeforms(const shader_t * shader)
+{
+	if(shader->numDeforms)
+	{
+		const deformStage_t *ds = &shader->deforms[0];
+
+		if (shader->numDeforms > 1)
+			return qtrue;
+
+		switch (ds->deformation)
+		{
+			case DEFORM_WAVE:
+			case DEFORM_BULGE:
+				return qfalse;
+
+			default:
+				return qtrue;
+		}
+	}
+
+	return qfalse;
+}
+
+typedef struct shaderState_s {
+  char shaderName[MAX_QPATH];     // name of shader this state belongs to
+  char name[MAX_STATE_NAME];      // name of this state
+  char stateShader[MAX_QPATH];    // shader this name invokes
+  int cycleTime;                  // time this cycle lasts, <= 0 is forever
+  shader_t *shader;
+} shaderState_t;
+
+enum
+{
+	ATTR_INDEX_POSITION       = 0,
+	ATTR_INDEX_TEXCOORD0      = 1,
+	ATTR_INDEX_TEXCOORD1      = 2,
+	ATTR_INDEX_TANGENT        = 3,
+	ATTR_INDEX_BITANGENT      = 4,
+	ATTR_INDEX_NORMAL         = 5,
+	ATTR_INDEX_COLOR          = 6,
+	ATTR_INDEX_PAINTCOLOR     = 7,
+	ATTR_INDEX_LIGHTDIRECTION = 8,
+	ATTR_INDEX_BONE_INDEXES   = 9,
+	ATTR_INDEX_BONE_WEIGHTS   = 10,
+
+	// GPU vertex animations
+	ATTR_INDEX_POSITION2      = 11,
+	ATTR_INDEX_TANGENT2       = 12,
+	ATTR_INDEX_BITANGENT2     = 13,
+	ATTR_INDEX_NORMAL2        = 14
+};
+
+enum
+{
+	GLS_SRCBLEND_ZERO					= (1 << 0),
+	GLS_SRCBLEND_ONE					= (1 << 1),
+	GLS_SRCBLEND_DST_COLOR				= (1 << 2),
+	GLS_SRCBLEND_ONE_MINUS_DST_COLOR	= (1 << 3),
+	GLS_SRCBLEND_SRC_ALPHA				= (1 << 4),
+	GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA	= (1 << 5),
+	GLS_SRCBLEND_DST_ALPHA				= (1 << 6),
+	GLS_SRCBLEND_ONE_MINUS_DST_ALPHA	= (1 << 7),
+	GLS_SRCBLEND_ALPHA_SATURATE			= (1 << 8),
+
+	GLS_SRCBLEND_BITS					= GLS_SRCBLEND_ZERO
+											| GLS_SRCBLEND_ONE
+											| GLS_SRCBLEND_DST_COLOR
+											| GLS_SRCBLEND_ONE_MINUS_DST_COLOR
+											| GLS_SRCBLEND_SRC_ALPHA
+											| GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA
+											| GLS_SRCBLEND_DST_ALPHA
+											| GLS_SRCBLEND_ONE_MINUS_DST_ALPHA
+											| GLS_SRCBLEND_ALPHA_SATURATE,
+
+	GLS_DSTBLEND_ZERO					= (1 << 9),
+	GLS_DSTBLEND_ONE					= (1 << 10),
+	GLS_DSTBLEND_SRC_COLOR				= (1 << 11),
+	GLS_DSTBLEND_ONE_MINUS_SRC_COLOR	= (1 << 12),
+	GLS_DSTBLEND_SRC_ALPHA				= (1 << 13),
+	GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA	= (1 << 14),
+	GLS_DSTBLEND_DST_ALPHA				= (1 << 15),
+	GLS_DSTBLEND_ONE_MINUS_DST_ALPHA	= (1 << 16),
+
+	GLS_DSTBLEND_BITS					= GLS_DSTBLEND_ZERO
+											| GLS_DSTBLEND_ONE
+											| GLS_DSTBLEND_SRC_COLOR
+											| GLS_DSTBLEND_ONE_MINUS_SRC_COLOR
+											| GLS_DSTBLEND_SRC_ALPHA
+											| GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA
+											| GLS_DSTBLEND_DST_ALPHA
+											| GLS_DSTBLEND_ONE_MINUS_DST_ALPHA,
+
+	GLS_DEPTHMASK_TRUE					= (1 << 17),
+
+	GLS_POLYMODE_LINE					= (1 << 18),
+
+	GLS_DEPTHTEST_DISABLE				= (1 << 19),
+
+	GLS_DEPTHFUNC_LESS					= (1 << 20),
+	GLS_DEPTHFUNC_EQUAL					= (1 << 21),
+
+	GLS_DEPTHFUNC_BITS					= GLS_DEPTHFUNC_LESS
+											| GLS_DEPTHFUNC_EQUAL,
+
+	GLS_ATEST_GT_0						= (1 << 22),
+	GLS_ATEST_LT_128					= (1 << 23),
+	GLS_ATEST_GE_128					= (1 << 24),
+//	GLS_ATEST_GE_CUSTOM					= (1 << 25),
+
+	GLS_ATEST_BITS						= GLS_ATEST_GT_0
+											| GLS_ATEST_LT_128
+											| GLS_ATEST_GE_128,
+//											| GLS_ATEST_GT_CUSTOM,
+
+	GLS_REDMASK_FALSE					= (1 << 26),
+	GLS_GREENMASK_FALSE					= (1 << 27),
+	GLS_BLUEMASK_FALSE					= (1 << 28),
+	GLS_ALPHAMASK_FALSE					= (1 << 29),
+
+	GLS_COLORMASK_BITS					= GLS_REDMASK_FALSE
+											| GLS_GREENMASK_FALSE
+											| GLS_BLUEMASK_FALSE
+											| GLS_ALPHAMASK_FALSE,
+
+	GLS_STENCILTEST_ENABLE				= (1 << 30),
+
+	GLS_DEFAULT							= GLS_DEPTHMASK_TRUE
+};
+
+enum
+{
+	ATTR_POSITION =       0x0001,
+	ATTR_TEXCOORD =       0x0002,
+	ATTR_LIGHTCOORD =     0x0004,
+	ATTR_TANGENT =        0x0008,
+	ATTR_BITANGENT =      0x0010,
+	ATTR_NORMAL =         0x0020,
+	ATTR_COLOR =          0x0040,
+	ATTR_PAINTCOLOR =     0x0080,
+	ATTR_LIGHTDIRECTION = 0x0100,
+	ATTR_BONE_INDEXES =   0x0200,
+	ATTR_BONE_WEIGHTS =   0x0400,
+
+	// for .md3 interpolation
+	ATTR_POSITION2 =      0x0800,
+	ATTR_TANGENT2 =       0x1000,
+	ATTR_BITANGENT2 =     0x2000,
+	ATTR_NORMAL2 =        0x4000,
+
+	ATTR_DEFAULT = ATTR_POSITION,
+	ATTR_BITS =	ATTR_POSITION |
+				ATTR_TEXCOORD |
+				ATTR_LIGHTCOORD |
+				ATTR_TANGENT |
+				ATTR_BITANGENT |
+				ATTR_NORMAL |
+				ATTR_COLOR |
+				ATTR_PAINTCOLOR |
+				ATTR_LIGHTDIRECTION |
+				ATTR_BONE_INDEXES |
+				ATTR_BONE_WEIGHTS |
+				ATTR_POSITION2 |
+				ATTR_TANGENT2 |
+				ATTR_BITANGENT2 |
+				ATTR_NORMAL2
+};
+
+enum
+{
+	GENERICDEF_USE_DEFORM_VERTEXES  = 0x0001,
+	GENERICDEF_USE_TCGEN            = 0x0002,
+	GENERICDEF_USE_VERTEX_ANIMATION = 0x0004,
+	GENERICDEF_USE_FOG              = 0x0008,
+	GENERICDEF_USE_RGBAGEN          = 0x0010,
+	GENERICDEF_USE_LIGHTMAP         = 0x0020,
+	GENERICDEF_ALL                  = 0x003F,
+	GENERICDEF_COUNT                = 0x0040,
+};
+
+enum
+{
+	LIGHTDEF_USE_LIGHTMAP      = 0x0001,
+	LIGHTDEF_USE_LIGHT_VECTOR  = 0x0002,
+	LIGHTDEF_USE_LIGHT_VERTEX  = 0x0003,
+	LIGHTDEF_LIGHTTYPE_MASK    = 0x0003,
+	LIGHTDEF_USE_NORMALMAP     = 0x0004,
+	LIGHTDEF_USE_SPECULARMAP   = 0x0008,
+	LIGHTDEF_USE_DELUXEMAP     = 0x0010,
+	LIGHTDEF_USE_PARALLAXMAP   = 0x0020,
+	LIGHTDEF_USE_SHADOWMAP     = 0x0040,
+	LIGHTDEF_TCGEN_ENVIRONMENT = 0x0080,
+	LIGHTDEF_ENTITY            = 0x0100,
+	LIGHTDEF_ALL               = 0x01FF,
+	LIGHTDEF_COUNT             = 0x0200
+};
+
+enum
+{
+	GLSL_INT,
+	GLSL_FLOAT,
+	GLSL_FLOAT5,
+	GLSL_VEC2,
+	GLSL_VEC3,
+	GLSL_VEC4,
+	GLSL_MAT16
+};
+
+// Tr3B - shaderProgram_t represents a pair of one
+// GLSL vertex and one GLSL fragment shader
+typedef struct shaderProgram_s
+{
+	char            name[MAX_QPATH];
+
+	GLhandleARB     program;
+	GLhandleARB     vertexShader;
+	GLhandleARB     fragmentShader;
+	uint32_t        attribs;	// vertex array attributes
+
+	// uniform parameters
+	int   numUniforms;
+	GLint *uniforms;
+	GLint *uniformTypes;
+	int   *uniformBufferOffsets;
+	char  *uniformBuffer;
+} shaderProgram_t;
+
+
+enum
+{
+	TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX = 0,
+	TEXTURECOLOR_UNIFORM_INVTEXRES,
+	TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX,
+	TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR,
+	TEXTURECOLOR_UNIFORM_TEXTUREMAP,
+	TEXTURECOLOR_UNIFORM_LEVELSMAP,
+	TEXTURECOLOR_UNIFORM_COLOR,
+	TEXTURECOLOR_UNIFORM_COUNT
+};
+
+
+enum
+{
+	FOGPASS_UNIFORM_FOGDISTANCE = 0,
+	FOGPASS_UNIFORM_FOGDEPTH,
+	FOGPASS_UNIFORM_FOGEYET,
+	FOGPASS_UNIFORM_DEFORMGEN,
+	FOGPASS_UNIFORM_DEFORMPARAMS,
+	FOGPASS_UNIFORM_TIME,
+	FOGPASS_UNIFORM_COLOR,
+	FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX,
+	FOGPASS_UNIFORM_VERTEXLERP,
+	FOGPASS_UNIFORM_COUNT
+};
+
+
+enum
+{
+	DLIGHT_UNIFORM_DIFFUSEMAP = 0,
+	DLIGHT_UNIFORM_DLIGHTINFO,
+	DLIGHT_UNIFORM_DEFORMGEN,
+	DLIGHT_UNIFORM_DEFORMPARAMS,
+	DLIGHT_UNIFORM_TIME,
+	DLIGHT_UNIFORM_COLOR,
+	DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX,
+	DLIGHT_UNIFORM_VERTEXLERP,
+	DLIGHT_UNIFORM_COUNT
+};
+
+
+enum
+{
+	PSHADOW_UNIFORM_SHADOWMAP = 0,
+	PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX,
+	PSHADOW_UNIFORM_LIGHTFORWARD,
+	PSHADOW_UNIFORM_LIGHTUP,
+	PSHADOW_UNIFORM_LIGHTRIGHT,
+	PSHADOW_UNIFORM_LIGHTORIGIN,
+	PSHADOW_UNIFORM_LIGHTRADIUS,
+	PSHADOW_UNIFORM_COUNT
+};
+
+
+enum
+{
+	GENERIC_UNIFORM_DIFFUSEMAP = 0,
+	GENERIC_UNIFORM_LIGHTMAP,
+	GENERIC_UNIFORM_NORMALMAP,
+	GENERIC_UNIFORM_DELUXEMAP,
+	GENERIC_UNIFORM_SPECULARMAP,
+	GENERIC_UNIFORM_SHADOWMAP,
+	GENERIC_UNIFORM_DIFFUSETEXMATRIX,
+	//GENERIC_UNIFORM_NORMALTEXMATRIX,
+	//GENERIC_UNIFORM_SPECULARTEXMATRIX,
+	GENERIC_UNIFORM_TEXTURE1ENV,
+	GENERIC_UNIFORM_VIEWORIGIN,
+	GENERIC_UNIFORM_TCGEN0,
+	GENERIC_UNIFORM_TCGEN0VECTOR0,
+	GENERIC_UNIFORM_TCGEN0VECTOR1,
+	GENERIC_UNIFORM_DEFORMGEN,
+	GENERIC_UNIFORM_DEFORMPARAMS,
+	GENERIC_UNIFORM_COLORGEN,
+	GENERIC_UNIFORM_ALPHAGEN,
+	GENERIC_UNIFORM_BASECOLOR,
+	GENERIC_UNIFORM_VERTCOLOR,
+	GENERIC_UNIFORM_AMBIENTLIGHT,
+	GENERIC_UNIFORM_DIRECTEDLIGHT,
+	GENERIC_UNIFORM_LIGHTORIGIN,
+	GENERIC_UNIFORM_LIGHTRADIUS,
+	GENERIC_UNIFORM_PORTALRANGE,
+	GENERIC_UNIFORM_FOGDISTANCE,
+	GENERIC_UNIFORM_FOGDEPTH,
+	GENERIC_UNIFORM_FOGEYET,
+	GENERIC_UNIFORM_FOGCOLORMASK,
+	GENERIC_UNIFORM_MODELMATRIX,
+	GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX,
+	GENERIC_UNIFORM_TIME,
+	GENERIC_UNIFORM_VERTEXLERP,
+	GENERIC_UNIFORM_MATERIALINFO,
+	GENERIC_UNIFORM_COUNT
+};
+
+enum
+{
+	SHADOWMASK_UNIFORM_SCREENDEPTHMAP = 0,
+	SHADOWMASK_UNIFORM_SHADOWMAP,
+	SHADOWMASK_UNIFORM_SHADOWMAP2,
+	SHADOWMASK_UNIFORM_SHADOWMAP3,
+	SHADOWMASK_UNIFORM_SHADOWMVP,
+	SHADOWMASK_UNIFORM_SHADOWMVP2,
+	SHADOWMASK_UNIFORM_SHADOWMVP3,
+	SHADOWMASK_UNIFORM_VIEWORIGIN,
+	SHADOWMASK_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2
+	SHADOWMASK_UNIFORM_VIEWFORWARD,
+	SHADOWMASK_UNIFORM_VIEWLEFT,
+	SHADOWMASK_UNIFORM_VIEWUP,
+	SHADOWMASK_UNIFORM_COUNT
+};
+
+enum
+{
+	SSAO_UNIFORM_SCREENDEPTHMAP = 0,
+	SSAO_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2
+	SSAO_UNIFORM_COUNT
+};
+
+enum
+{
+	DEPTHBLUR_UNIFORM_SCREENIMAGEMAP = 0,
+	DEPTHBLUR_UNIFORM_SCREENDEPTHMAP,
+	DEPTHBLUR_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2
+	DEPTHBLUR_UNIFORM_COUNT
+};
+
+//
+// Tr3B: these are fire wall functions to avoid expensive redundant glUniform* calls
+//#define USE_UNIFORM_FIREWALL 1
+//#define LOG_GLSL_UNIFORMS 1
+
+// trRefdef_t holds everything that comes in refdef_t,
+// as well as the locally generated scene information
+typedef struct {
+	int			x, y, width, height;
+	float		fov_x, fov_y;
+	vec3_t		vieworg;
+	vec3_t		viewaxis[3];		// transformation matrix
+
+	stereoFrame_t	stereoFrame;
+
+	int			time;				// time in milliseconds for shader effects and other time dependent rendering issues
+	int			rdflags;			// RDF_NOWORLDMODEL, etc
+
+	// 1 bits will prevent the associated area from rendering at all
+	byte		areamask[MAX_MAP_AREA_BYTES];
+	qboolean	areamaskModified;	// qtrue if areamask changed since last scene
+
+	float		floatTime;			// tr.refdef.time / 1000.0
+
+	float		blurFactor;
+
+	// text messages for deform text shaders
+	char		text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+
+	int			num_entities;
+	trRefEntity_t	*entities;
+
+	int			num_dlights;
+	struct dlight_s	*dlights;
+
+	int			numPolys;
+	struct srfPoly_s	*polys;
+
+	int			numDrawSurfs;
+	struct drawSurf_s	*drawSurfs;
+
+	unsigned int dlightMask;
+	int         num_pshadows;
+	struct pshadow_s *pshadows;
+
+	float       sunShadowMvp[3][16];
+	float       sunDir[4];
+	float       sunCol[4];
+	float       sunAmbCol[4];
+	float       colorScale;
+
+	float       autoExposureMinMax[2];
+	float       toneMinAvgMaxLinear[3];
+} trRefdef_t;
+
+
+//=================================================================================
+
+// skins allow models to be retextured without modifying the model file
+typedef struct {
+	char		name[MAX_QPATH];
+	shader_t	*shader;
+} skinSurface_t;
+
+typedef struct skin_s {
+	char		name[MAX_QPATH];		// game path, including extension
+	int			numSurfaces;
+	skinSurface_t	*surfaces[MD3_MAX_SURFACES];
+} skin_t;
+
+
+typedef struct {
+	int			originalBrushNumber;
+	vec3_t		bounds[2];
+
+	unsigned	colorInt;				// in packed byte format
+	float		tcScale;				// texture coordinate vector scales
+	fogParms_t	parms;
+
+	// for clipping distance in fog when outside
+	qboolean	hasSurface;
+	float		surface[4];
+} fog_t;
+
+typedef enum {
+	VPF_NONE         = 0x00,
+	VPF_SHADOWMAP    = 0x01,
+	VPF_DEPTHSHADOW  = 0x02,
+	VPF_DEPTHCLAMP   = 0x04,
+	VPF_ORTHOGRAPHIC = 0x08,
+	VPF_USESUNLIGHT  = 0x10,
+} viewParmFlags_t;
+
+typedef struct {
+	orientationr_t	or;
+	orientationr_t	world;
+	vec3_t		pvsOrigin;			// may be different than or.origin for portals
+	qboolean	isPortal;			// true if this view is through a portal
+	qboolean	isMirror;			// the portal is a mirror, invert the face culling
+	viewParmFlags_t flags;
+	int			frameSceneNum;		// copied from tr.frameSceneNum
+	int			frameCount;			// copied from tr.frameCount
+	cplane_t	portalPlane;		// clip anything behind this if mirroring
+	int			viewportX, viewportY, viewportWidth, viewportHeight;
+	FBO_t		*targetFbo;
+	float		fovX, fovY;
+	float		projectionMatrix[16];
+	cplane_t	frustum[5];
+	vec3_t		visBounds[2];
+	float		zFar;
+	float       zNear;
+	stereoFrame_t	stereoFrame;
+} viewParms_t;
+
+
+/*
+==============================================================================
+
+SURFACES
+
+==============================================================================
+*/
+typedef byte color4ub_t[4];
+
+// any changes in surfaceType must be mirrored in rb_surfaceTable[]
+typedef enum {
+	SF_BAD,
+	SF_SKIP,				// ignore
+	SF_FACE,
+	SF_GRID,
+	SF_TRIANGLES,
+	SF_POLY,
+	SF_MDV,
+	SF_MD4,
+#ifdef RAVENMD4
+	SF_MDR,
+#endif
+	SF_IQM,
+	SF_FLARE,
+	SF_ENTITY,				// beams, rails, lightning, etc that can be determined by entity
+	SF_DISPLAY_LIST,
+	SF_VBO_MESH,
+	SF_VBO_MDVMESH,
+
+	SF_NUM_SURFACE_TYPES,
+	SF_MAX = 0x7fffffff			// ensures that sizeof( surfaceType_t ) == sizeof( int )
+} surfaceType_t;
+
+typedef struct drawSurf_s {
+	unsigned			sort;			// bit combination for fast compares
+	surfaceType_t		*surface;		// any of surface*_t
+} drawSurf_t;
+
+#define	MAX_FACE_POINTS		64
+
+#define	MAX_PATCH_SIZE		32			// max dimensions of a patch mesh in map file
+#define	MAX_GRID_SIZE		65			// max dimensions of a grid mesh in memory
+
+// when cgame directly specifies a polygon, it becomes a srfPoly_t
+// as soon as it is called
+typedef struct srfPoly_s {
+	surfaceType_t	surfaceType;
+	qhandle_t		hShader;
+	int				fogIndex;
+	int				numVerts;
+	polyVert_t		*verts;
+} srfPoly_t;
+
+typedef struct srfDisplayList_s {
+	surfaceType_t	surfaceType;
+	int				listNum;
+} srfDisplayList_t;
+
+
+typedef struct srfFlare_s {
+	surfaceType_t	surfaceType;
+	vec3_t			origin;
+	vec3_t			normal;
+	vec3_t			color;
+} srfFlare_t;
+
+typedef struct
+{
+	vec3_t          xyz;
+	vec2_t          st;
+	vec2_t          lightmap;
+	vec3_t          normal;
+#ifdef USE_VERT_TANGENT_SPACE
+	vec3_t          tangent;
+	vec3_t          bitangent;
+#endif
+	vec3_t          lightdir;
+	vec4_t			vertexColors;
+
+#if DEBUG_OPTIMIZEVERTICES
+	unsigned int    id;
+#endif
+} srfVert_t;
+
+#ifdef USE_VERT_TANGENT_SPACE
+#define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0, 0}}
+#else
+#define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0},  {0, 0, 0, 0}}
+#endif
+
+typedef struct
+{
+	int             indexes[3];
+	int             neighbors[3];
+	vec4_t          plane;
+	qboolean        facingLight;
+	qboolean        degenerated;
+} srfTriangle_t;
+
+
+typedef struct srfGridMesh_s
+{
+	surfaceType_t   surfaceType;
+
+	// dynamic lighting information
+	int				dlightBits[SMP_FRAMES];
+	int             pshadowBits[SMP_FRAMES];
+
+	// culling information
+	vec3_t			meshBounds[2];
+	vec3_t			localOrigin;
+	float			meshRadius;
+
+	// lod information, which may be different
+	// than the culling information to allow for
+	// groups of curves that LOD as a unit
+	vec3_t			lodOrigin;
+	float			lodRadius;
+	int				lodFixed;
+	int				lodStitched;
+
+	// vertexes
+	int				width, height;
+	float			*widthLodError;
+	float			*heightLodError;
+
+	int             numTriangles;
+	srfTriangle_t  *triangles;
+
+	int             numVerts;
+	srfVert_t      *verts;
+
+	// BSP VBO offsets
+	int             firstVert;
+	int             firstIndex;
+
+	// static render data
+	VBO_t          *vbo;		// points to bsp model VBO
+	IBO_t          *ibo;
+} srfGridMesh_t;
+
+
+typedef struct
+{
+	surfaceType_t   surfaceType;
+
+	// dynamic lighting information
+	int			dlightBits[SMP_FRAMES];
+	int         pshadowBits[SMP_FRAMES];
+
+	// culling information
+	cplane_t        plane;
+//	vec3_t          bounds[2];
+
+	// triangle definitions
+	int             numTriangles;
+	srfTriangle_t  *triangles;
+
+	int             numVerts;
+	srfVert_t      *verts;
+
+	// BSP VBO offsets
+	int             firstVert;
+	int             firstIndex;
+
+	// static render data
+	VBO_t          *vbo;		// points to bsp model VBO
+	IBO_t          *ibo;
+} srfSurfaceFace_t;
+
+
+// misc_models in maps are turned into direct geometry by xmap
+typedef struct
+{
+	surfaceType_t   surfaceType;
+
+	// dynamic lighting information
+	int			dlightBits[SMP_FRAMES];
+	int         pshadowBits[SMP_FRAMES];
+
+	// culling information
+//	vec3_t          bounds[2];
+
+	// triangle definitions
+	int             numTriangles;
+	srfTriangle_t  *triangles;
+
+	int             numVerts;
+	srfVert_t      *verts;
+
+	// BSP VBO offsets
+	int             firstVert;
+	int             firstIndex;
+
+	// static render data
+	VBO_t          *vbo;		// points to bsp model VBO
+	IBO_t          *ibo;
+} srfTriangles_t;
+
+// inter-quake-model
+typedef struct {
+	int		num_vertexes;
+	int		num_triangles;
+	int		num_frames;
+	int		num_surfaces;
+	int		num_joints;
+	struct srfIQModel_s	*surfaces;
+
+	float		*positions;
+	float		*texcoords;
+	float		*normals;
+	float		*tangents;
+	byte		*blendIndexes;
+	byte		*blendWeights;
+	byte		*colors;
+	int		*triangles;
+
+	int		*jointParents;
+	float		*poseMats;
+	float		*bounds;
+	char		*names;
+} iqmData_t;
+
+// inter-quake-model surface
+typedef struct srfIQModel_s {
+	surfaceType_t	surfaceType;
+	char		name[MAX_QPATH];
+	shader_t	*shader;
+	iqmData_t	*data;
+	int		first_vertex, num_vertexes;
+	int		first_triangle, num_triangles;
+} srfIQModel_t;
+
+typedef struct srfVBOMesh_s
+{
+	surfaceType_t   surfaceType;
+
+	struct shader_s *shader;	// FIXME move this to somewhere else
+	int				fogIndex;
+
+	// dynamic lighting information
+	int			dlightBits[SMP_FRAMES];
+	int         pshadowBits[SMP_FRAMES];
+
+	// culling information
+	vec3_t          bounds[2];
+
+	// backEnd stats
+	int             numIndexes;
+	int             numVerts;
+	int				firstIndex;
+
+	// static render data
+	VBO_t          *vbo;
+	IBO_t          *ibo;
+} srfVBOMesh_t;
+
+typedef struct srfVBOMDVMesh_s
+{
+	surfaceType_t   surfaceType;
+
+	struct mdvModel_s *mdvModel;
+	struct mdvSurface_s *mdvSurface;
+
+	// backEnd stats
+	int             numIndexes;
+	int             numVerts;
+
+	// static render data
+	VBO_t          *vbo;
+	IBO_t          *ibo;
+} srfVBOMDVMesh_t;
+
+extern	void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *);
+
+/*
+==============================================================================
+
+SHADOWS
+
+==============================================================================
+*/
+
+typedef struct pshadow_s
+{
+	float sort;
+	
+	int    numEntities;
+	int    entityNums[8];
+	vec3_t entityOrigins[8];
+	float  entityRadiuses[8];
+
+	float viewRadius;
+	vec3_t viewOrigin;
+
+	vec3_t lightViewAxis[3];
+	vec3_t lightOrigin;
+	float  lightRadius;
+	cplane_t cullPlane;
+} pshadow_t;
+
+
+/*
+==============================================================================
+
+BRUSH MODELS
+
+==============================================================================
+*/
+
+
+//
+// in memory representation
+//
+
+#define	SIDE_FRONT	0
+#define	SIDE_BACK	1
+#define	SIDE_ON		2
+
+#define CULLINFO_NONE   0
+#define CULLINFO_BOX    1
+#define CULLINFO_SPHERE 2
+#define CULLINFO_PLANE  4
+
+typedef struct cullinfo_s {
+	int             type;
+	vec3_t          bounds[2];
+	vec3_t			localOrigin;
+	float			radius;
+	cplane_t        plane;
+} cullinfo_t;
+
+typedef struct msurface_s {
+	//int					viewCount;		// if == tr.viewCount, already added
+	struct shader_s		*shader;
+	int					fogIndex;
+	cullinfo_t          cullinfo;
+
+	surfaceType_t		*data;			// any of srf*_t
+} msurface_t;
+
+
+#define	CONTENTS_NODE		-1
+typedef struct mnode_s {
+	// common with leaf and node
+	int			contents;		// -1 for nodes, to differentiate from leafs
+	int             visCounts[MAX_VISCOUNTS];	// node needs to be traversed if current
+	vec3_t		mins, maxs;		// for bounding box culling
+	struct mnode_s	*parent;
+
+	// node specific
+	cplane_t	*plane;
+	struct mnode_s	*children[2];	
+
+	// leaf specific
+	int			cluster;
+	int			area;
+
+	int         firstmarksurface;
+	int			nummarksurfaces;
+} mnode_t;
+
+typedef struct {
+	vec3_t		bounds[2];		// for culling
+	int	        firstSurface;
+	int			numSurfaces;
+} bmodel_t;
+
+typedef struct {
+	char		name[MAX_QPATH];		// ie: maps/tim_dm2.bsp
+	char		baseName[MAX_QPATH];	// ie: tim_dm2
+
+	int			dataSize;
+
+	int			numShaders;
+	dshader_t	*shaders;
+
+	int			numBModels;
+	bmodel_t	*bmodels;
+
+	int			numplanes;
+	cplane_t	*planes;
+
+	int			numnodes;		// includes leafs
+	int			numDecisionNodes;
+	mnode_t		*nodes;
+
+	VBO_t          *vbo;
+	IBO_t          *ibo;
+
+	int         numWorldSurfaces;
+
+	int			numsurfaces;
+	msurface_t	*surfaces;
+	int         *surfacesViewCount;
+	int         *surfacesDlightBits;
+	int			*surfacesPshadowBits;
+
+	int			numMergedSurfaces;
+	msurface_t	*mergedSurfaces;
+	int         *mergedSurfacesViewCount;
+	int         *mergedSurfacesDlightBits;
+	int			*mergedSurfacesPshadowBits;
+
+	int			nummarksurfaces;
+	int         *marksurfaces;
+	int         *viewSurfaces;
+
+	int			numfogs;
+	fog_t		*fogs;
+
+	vec3_t		lightGridOrigin;
+	vec3_t		lightGridSize;
+	vec3_t		lightGridInverseSize;
+	int			lightGridBounds[3];
+	byte		*lightGridData;
+	float		*hdrLightGrid;
+
+
+	int			numClusters;
+	int			clusterBytes;
+	const byte	*vis;			// may be passed in by CM_LoadMap to save space
+
+	byte		*novis;			// clusterBytes of 0xff
+
+	char		*entityString;
+	char		*entityParsePoint;
+} world_t;
+
+
+/*
+==============================================================================
+MDV MODELS - meta format for vertex animation models like .md2, .md3, .mdc
+==============================================================================
+*/
+typedef struct
+{
+	float           bounds[2][3];
+	float           localOrigin[3];
+	float           radius;
+} mdvFrame_t;
+
+typedef struct
+{
+	float           origin[3];
+	float           axis[3][3];
+} mdvTag_t;
+
+typedef struct
+{
+	char            name[MAX_QPATH];	// tag name
+} mdvTagName_t;
+
+typedef struct
+{
+	vec3_t          xyz;
+	vec3_t          normal;
+#ifdef USE_VERT_TANGENT_SPACE
+	vec3_t          tangent;
+	vec3_t          bitangent;
+#endif
+} mdvVertex_t;
+
+typedef struct
+{
+	float           st[2];
+} mdvSt_t;
+
+typedef struct mdvSurface_s
+{
+	surfaceType_t   surfaceType;
+
+	char            name[MAX_QPATH];	// polyset name
+
+	int             numShaderIndexes;
+	int				*shaderIndexes;
+
+	int             numVerts;
+	mdvVertex_t    *verts;
+	mdvSt_t        *st;
+
+	int             numTriangles;
+	srfTriangle_t  *triangles;
+
+	struct mdvModel_s *model;
+} mdvSurface_t;
+
+typedef struct mdvModel_s
+{
+	int             numFrames;
+	mdvFrame_t     *frames;
+
+	int             numTags;
+	mdvTag_t       *tags;
+	mdvTagName_t   *tagNames;
+
+	int             numSurfaces;
+	mdvSurface_t   *surfaces;
+
+	int             numVBOSurfaces;
+	srfVBOMDVMesh_t  *vboSurfaces;
+
+	int             numSkins;
+} mdvModel_t;
+
+
+//======================================================================
+
+typedef enum {
+	MOD_BAD,
+	MOD_BRUSH,
+	MOD_MESH,
+	MOD_MD4,
+#ifdef RAVENMD4
+	MOD_MDR,
+#endif
+	MOD_IQM
+} modtype_t;
+
+typedef struct model_s {
+	char		name[MAX_QPATH];
+	modtype_t	type;
+	int			index;		// model = tr.models[model->index]
+
+	int			dataSize;	// just for listing purposes
+	bmodel_t	*bmodel;		// only if type == MOD_BRUSH
+	mdvModel_t	*mdv[MD3_MAX_LODS];	// only if type == MOD_MESH
+	void	*modelData;			// only if type == (MOD_MD4 | MOD_MDR | MOD_IQM)
+
+	int			 numLods;
+} model_t;
+
+
+#define	MAX_MOD_KNOWN	1024
+
+void		R_ModelInit (void);
+model_t		*R_GetModelByHandle( qhandle_t hModel );
+int			R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, 
+					 float frac, const char *tagName );
+void		R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs );
+
+void		R_Modellist_f (void);
+
+//====================================================
+extern	refimport_t		ri;
+
+#define	MAX_DRAWIMAGES			2048
+#define	MAX_SKINS				1024
+
+
+#define	MAX_DRAWSURFS			0x10000
+#define	DRAWSURF_MASK			(MAX_DRAWSURFS-1)
+
+/*
+
+the drawsurf sort data is packed into a single 32 bit value so it can be
+compared quickly during the qsorting process
+
+the bits are allocated as follows:
+
+0 - 1	: dlightmap index
+//2		: used to be clipped flag REMOVED - 03.21.00 rad
+2 - 6	: fog index
+11 - 20	: entity index
+21 - 31	: sorted shader index
+
+	TTimo - 1.32
+0-1   : dlightmap index
+2-6   : fog index
+7-16  : entity index
+17-30 : sorted shader index
+
+    SmileTheory - for pshadows
+17-31 : sorted shader index
+7-16  : entity index
+2-6   : fog index
+1     : pshadow flag
+0     : dlight flag
+*/
+#define	QSORT_FOGNUM_SHIFT	2
+#define	QSORT_REFENTITYNUM_SHIFT	7
+#define	QSORT_SHADERNUM_SHIFT	(QSORT_REFENTITYNUM_SHIFT+REFENTITYNUM_BITS)
+#if (QSORT_SHADERNUM_SHIFT+SHADERNUM_BITS) > 32
+	#error "Need to update sorting, too many bits."
+#endif
+#define QSORT_PSHADOW_SHIFT     1
+
+extern	int			gl_filter_min, gl_filter_max;
+
+/*
+** performanceCounters_t
+*/
+typedef struct {
+	int		c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out;
+	int		c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out;
+	int		c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out;
+	int		c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out;
+
+	int		c_leafs;
+	int		c_dlightSurfaces;
+	int		c_dlightSurfacesCulled;
+} frontEndCounters_t;
+
+#define	FOG_TABLE_SIZE		256
+#define FUNCTABLE_SIZE		1024
+#define FUNCTABLE_SIZE2		10
+#define FUNCTABLE_MASK		(FUNCTABLE_SIZE-1)
+
+
+// the renderer front end should never modify glstate_t
+typedef struct {
+	int			currenttextures[NUM_TEXTURE_BUNDLES];
+	int			currenttmu;
+	qboolean	finishCalled;
+	int			texEnv[2];
+	int			faceCulling;
+	unsigned long	glStateBits;
+	uint32_t		vertexAttribsState;
+	uint32_t		vertexAttribPointersSet;
+	uint32_t        vertexAttribsNewFrame;
+	uint32_t        vertexAttribsOldFrame;
+	float           vertexAttribsInterpolation;
+	shaderProgram_t *currentProgram;
+	FBO_t          *currentFBO;
+	VBO_t          *currentVBO;
+	IBO_t          *currentIBO;
+	matrix_t        modelview;
+	matrix_t        projection;
+	matrix_t		modelviewProjection;
+} glstate_t;
+
+typedef enum {
+	MI_NONE,
+	MI_NVX,
+	MI_ATI
+} memInfo_t;
+
+typedef enum {
+	TCR_NONE = 0x0000,
+	TCR_LATC = 0x0001,
+	TCR_BPTC = 0x0002,
+} textureCompressionRef_t;
+
+// We can't change glConfig_t without breaking DLL/vms compatibility, so
+// store extensions we have here.
+typedef struct {
+	qboolean    drawRangeElements;
+	qboolean    multiDrawArrays;
+	qboolean	occlusionQuery;
+
+	int glslMajorVersion;
+	int glslMinorVersion;
+
+	memInfo_t   memInfo;
+
+	qboolean framebufferObject;
+	int maxRenderbufferSize;
+	int maxColorAttachments;
+
+	qboolean textureNonPowerOfTwo;
+	qboolean textureFloat;
+	qboolean halfFloatPixel;
+	qboolean packedDepthStencil;
+	textureCompressionRef_t textureCompression;
+	
+	qboolean framebufferMultisample;
+	qboolean framebufferBlit;
+
+	qboolean texture_srgb;
+	qboolean framebuffer_srgb;
+
+	qboolean depthClamp;
+} glRefConfig_t;
+
+
+typedef struct {
+	int		c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes;
+	int     c_surfBatches;
+	float	c_overDraw;
+	
+	int		c_vboVertexBuffers;
+	int		c_vboIndexBuffers;
+	int		c_vboVertexes;
+	int		c_vboIndexes;
+
+	int     c_staticVboDraws;
+	int     c_dynamicVboDraws;
+
+	int     c_multidraws;
+	int     c_multidrawsMerged;
+
+	int		c_dlightVertexes;
+	int		c_dlightIndexes;
+
+	int		c_flareAdds;
+	int		c_flareTests;
+	int		c_flareRenders;
+
+	int     c_glslShaderBinds;
+	int     c_genericDraws;
+	int     c_lightallDraws;
+	int     c_fogDraws;
+	int     c_dlightDraws;
+
+	int		msec;			// total msec for backend run
+} backEndCounters_t;
+
+// all state modified by the back end is seperated
+// from the front end state
+typedef struct {
+	int			smpFrame;
+	trRefdef_t	refdef;
+	viewParms_t	viewParms;
+	orientationr_t	or;
+	backEndCounters_t	pc;
+	qboolean	isHyperspace;
+	trRefEntity_t	*currentEntity;
+	qboolean	skyRenderedThisView;	// flag for drawing sun
+
+#ifdef REACTION
+	vec3_t					sunFlarePos;
+	qboolean				viewHasSunFlare;
+	qboolean                frameHasSunFlare;
+#endif
+
+	qboolean	projection2D;	// if qtrue, drawstretchpic doesn't need to change modes
+	byte		color2D[4];
+	qboolean	vertexes2D;		// shader needs to be finished
+	trRefEntity_t	entity2D;	// currentEntity will point at this when doing 2D rendering
+
+	FBO_t *last2DFBO;
+	qboolean    colorMask[4];
+	qboolean    framePostProcessed;
+	qboolean    depthFill;
+} backEndState_t;
+
+/*
+** trGlobals_t 
+**
+** Most renderer globals are defined here.
+** backend functions should never modify any of these fields,
+** but may read fields that aren't dynamically modified
+** by the frontend.
+*/
+typedef struct {
+	qboolean				registered;		// cleared at shutdown, set at beginRegistration
+
+	int						visIndex;
+	int						visClusters[MAX_VISCOUNTS];
+	int						visCounts[MAX_VISCOUNTS];	// incremented every time a new vis cluster is entered
+
+	int						frameCount;		// incremented every frame
+	int						sceneCount;		// incremented every scene
+	int						viewCount;		// incremented every view (twice a scene if portaled)
+											// and every R_MarkFragments call
+
+	int						smpFrame;		// toggles from 0 to 1 every endFrame
+
+	int						frameSceneNum;	// zeroed at RE_BeginFrame
+
+	qboolean				worldMapLoaded;
+	qboolean				worldDeluxeMapping;
+	vec2_t                  autoExposureMinMax;
+	vec3_t                  toneMinAvgMaxLevel;
+	world_t					*world;
+
+	const byte				*externalVisData;	// from RE_SetWorldVisData, shared with CM_Load
+
+	image_t					*defaultImage;
+	image_t					*scratchImage[32];
+	image_t					*fogImage;
+	image_t					*dlightImage;	// inverse-quare highlight for projective adding
+	image_t					*flareImage;
+	image_t					*whiteImage;			// full of 0xff
+	image_t					*identityLightImage;	// full of tr.identityLightByte
+
+	image_t                 *shadowCubemaps[MAX_DLIGHTS];
+	
+
+	image_t					*renderImage;
+	image_t					*godRaysImage;
+	image_t					*renderDepthImage;
+	image_t					*pshadowMaps[MAX_DRAWN_PSHADOWS];
+	image_t					*textureScratchImage[2];
+	image_t					*screenScratchImage;
+	image_t                 *quarterImage[2];
+	image_t					*calcLevelsImage;
+	image_t					*targetLevelsImage;
+	image_t					*fixedLevelsImage;
+	image_t					*sunShadowDepthImage[3];
+	image_t                 *screenShadowImage;
+	image_t                 *screenSsaoImage;
+	image_t					*hdrDepthImage;
+	
+	image_t					*textureDepthImage;
+
+	FBO_t					*renderFbo;
+	FBO_t					*msaaResolveFbo;
+	FBO_t					*godRaysFbo;
+	FBO_t					*depthFbo;
+	FBO_t					*pshadowFbos[MAX_DRAWN_PSHADOWS];
+	FBO_t					*textureScratchFbo[2];
+	FBO_t					*screenScratchFbo;
+	FBO_t                   *quarterFbo[2];
+	FBO_t					*calcLevelsFbo;
+	FBO_t					*targetLevelsFbo;
+	FBO_t					*sunShadowFbo[3];
+	FBO_t					*screenShadowFbo;
+	FBO_t					*screenSsaoFbo;
+	FBO_t					*hdrDepthFbo;
+
+	shader_t				*defaultShader;
+	shader_t				*shadowShader;
+	shader_t				*projectionShadowShader;
+
+	shader_t				*flareShader;
+	shader_t				*sunShader;
+
+	int						numLightmaps;
+	int						lightmapSize;
+	image_t					**lightmaps;
+	image_t					**deluxemaps;
+
+	int                     fatLightmapSize;
+	int		                fatLightmapStep;
+
+	trRefEntity_t			*currentEntity;
+	trRefEntity_t			worldEntity;		// point currentEntity at this when rendering world
+	int						currentEntityNum;
+	int						shiftedEntityNum;	// currentEntityNum << QSORT_REFENTITYNUM_SHIFT
+	model_t					*currentModel;
+
+	//
+	// GPU shader programs
+	//
+	shaderProgram_t genericShader[GENERICDEF_COUNT];
+	shaderProgram_t textureColorShader;
+	shaderProgram_t fogShader;
+	shaderProgram_t dlightallShader;
+	shaderProgram_t lightallShader[LIGHTDEF_COUNT];
+	shaderProgram_t shadowmapShader;
+	shaderProgram_t pshadowShader;
+	shaderProgram_t down4xShader;
+	shaderProgram_t bokehShader;
+	shaderProgram_t tonemapShader;
+	shaderProgram_t calclevels4xShader[2];
+	shaderProgram_t shadowmaskShader;
+	shaderProgram_t ssaoShader;
+	shaderProgram_t depthBlurShader[2];
+
+
+	// -----------------------------------------
+
+	viewParms_t				viewParms;
+
+	float					identityLight;		// 1.0 / ( 1 << overbrightBits )
+	int						identityLightByte;	// identityLight * 255
+	int						overbrightBits;		// r_overbrightBits->integer, but set to 0 if no hw gamma
+
+	orientationr_t			or;					// for current entity
+
+	trRefdef_t				refdef;
+
+	int						viewCluster;
+
+	float                   mapLightScale;
+
+	qboolean                sunShadows;
+	vec3_t					sunLight;			// from the sky shader for this level
+	vec3_t					sunAmbient;
+	vec3_t					sunDirection;
+
+	frontEndCounters_t		pc;
+	int						frontEndMsec;		// not in pc due to clearing issue
+
+	//
+	// put large tables at the end, so most elements will be
+	// within the +/32K indexed range on risc processors
+	//
+	model_t					*models[MAX_MOD_KNOWN];
+	int						numModels;
+
+	int						numImages;
+	image_t					*images[MAX_DRAWIMAGES];
+
+	int						numFBOs;
+	FBO_t					*fbos[MAX_FBOS];
+
+	int						numVBOs;
+	VBO_t					*vbos[MAX_VBOS];
+
+	int						numIBOs;
+	IBO_t					*ibos[MAX_IBOS];
+
+	// shader indexes from other modules will be looked up in tr.shaders[]
+	// shader indexes from drawsurfs will be looked up in sortedShaders[]
+	// lower indexed sortedShaders must be rendered first (opaque surfaces before translucent)
+	int						numShaders;
+	shader_t				*shaders[MAX_SHADERS];
+	shader_t				*sortedShaders[MAX_SHADERS];
+
+	int						numSkins;
+	skin_t					*skins[MAX_SKINS];
+
+#ifdef REACTION
+	GLuint					sunFlareQuery[2];
+	int						sunFlareQueryIndex;
+	qboolean				sunFlareQueryActive[2];
+#endif
+
+	float					sinTable[FUNCTABLE_SIZE];
+	float					squareTable[FUNCTABLE_SIZE];
+	float					triangleTable[FUNCTABLE_SIZE];
+	float					sawToothTable[FUNCTABLE_SIZE];
+	float					inverseSawToothTable[FUNCTABLE_SIZE];
+	float					fogTable[FOG_TABLE_SIZE];
+} trGlobals_t;
+
+extern backEndState_t	backEnd;
+extern trGlobals_t	tr;
+extern glconfig_t	glConfig;		// outside of TR since it shouldn't be cleared during ref re-init
+extern glstate_t	glState;		// outside of TR since it shouldn't be cleared during ref re-init
+
+// These three variables should live inside glConfig but can't because of compatibility issues to the original ID vms.
+// If you release a stand-alone game and your mod uses tr_types.h from this build you can safely move them to
+// the glconfig_t struct.
+extern qboolean  textureFilterAnisotropic;
+extern int       maxAnisotropy;
+extern glRefConfig_t glRefConfig;
+extern float     displayAspect;
+
+
+//
+// cvars
+//
+extern cvar_t	*r_flareSize;
+extern cvar_t	*r_flareFade;
+// coefficient for the flare intensity falloff function.
+#define FLARE_STDCOEFF "150"
+extern cvar_t	*r_flareCoeff;
+
+extern cvar_t	*r_railWidth;
+extern cvar_t	*r_railCoreWidth;
+extern cvar_t	*r_railSegmentLength;
+
+extern cvar_t	*r_ignore;				// used for debugging anything
+extern cvar_t	*r_verbose;				// used for verbose debug spew
+
+extern cvar_t	*r_znear;				// near Z clip plane
+extern cvar_t	*r_zproj;				// z distance of projection plane
+extern cvar_t	*r_stereoSeparation;			// separation of cameras for stereo rendering
+
+extern cvar_t	*r_stencilbits;			// number of desired stencil bits
+extern cvar_t	*r_depthbits;			// number of desired depth bits
+extern cvar_t	*r_colorbits;			// number of desired color bits, only relevant for fullscreen
+extern cvar_t	*r_texturebits;			// number of desired texture bits
+extern cvar_t	*r_ext_multisample;
+										// 0 = use framebuffer depth
+										// 16 = use 16-bit textures
+										// 32 = use 32-bit textures
+										// all else = error
+
+extern cvar_t	*r_measureOverdraw;		// enables stencil buffer overdraw measurement
+
+extern cvar_t	*r_lodbias;				// push/pull LOD transitions
+extern cvar_t	*r_lodscale;
+
+extern cvar_t	*r_inGameVideo;				// controls whether in game video should be draw
+extern cvar_t	*r_fastsky;				// controls whether sky should be cleared or drawn
+extern cvar_t	*r_drawSun;				// controls drawing of sun quad
+extern cvar_t	*r_dynamiclight;		// dynamic lights enabled/disabled
+extern cvar_t	*r_dlightBacks;			// dlight non-facing surfaces for continuity
+
+extern	cvar_t	*r_norefresh;			// bypasses the ref rendering
+extern	cvar_t	*r_drawentities;		// disable/enable entity rendering
+extern	cvar_t	*r_drawworld;			// disable/enable world rendering
+extern	cvar_t	*r_speeds;				// various levels of information display
+extern  cvar_t	*r_detailTextures;		// enables/disables detail texturing stages
+extern	cvar_t	*r_novis;				// disable/enable usage of PVS
+extern	cvar_t	*r_nocull;
+extern	cvar_t	*r_facePlaneCull;		// enables culling of planar surfaces with back side test
+extern	cvar_t	*r_nocurves;
+extern	cvar_t	*r_showcluster;
+
+extern cvar_t	*r_mode;				// video mode
+extern cvar_t	*r_fullscreen;
+extern cvar_t	*r_noborder;
+extern cvar_t	*r_gamma;
+extern cvar_t	*r_ignorehwgamma;		// overrides hardware gamma capabilities
+
+extern cvar_t	*r_allowExtensions;				// global enable/disable of OpenGL extensions
+extern cvar_t	*r_ext_compressed_textures;		// these control use of specific extensions
+extern cvar_t	*r_ext_multitexture;
+extern cvar_t	*r_ext_compiled_vertex_array;
+extern cvar_t	*r_ext_texture_env_add;
+
+extern cvar_t	*r_ext_texture_filter_anisotropic;
+extern cvar_t	*r_ext_max_anisotropy;
+
+extern  cvar_t  *r_ext_draw_range_elements;
+extern  cvar_t  *r_ext_multi_draw_arrays;
+extern  cvar_t  *r_ext_framebuffer_object;
+extern  cvar_t  *r_ext_texture_float;
+extern  cvar_t  *r_arb_half_float_pixel;
+extern  cvar_t  *r_ext_framebuffer_multisample;
+
+extern	cvar_t	*r_nobind;						// turns off binding to appropriate textures
+extern	cvar_t	*r_singleShader;				// make most world faces use default shader
+extern	cvar_t	*r_roundImagesDown;
+extern	cvar_t	*r_colorMipLevels;				// development aid to see texture mip usage
+extern	cvar_t	*r_picmip;						// controls picmip values
+extern	cvar_t	*r_finish;
+extern	cvar_t	*r_drawBuffer;
+extern	cvar_t	*r_swapInterval;
+extern	cvar_t	*r_textureMode;
+extern	cvar_t	*r_offsetFactor;
+extern	cvar_t	*r_offsetUnits;
+
+extern	cvar_t	*r_fullbright;					// avoid lightmap pass
+extern	cvar_t	*r_lightmap;					// render lightmaps only
+extern	cvar_t	*r_vertexLight;					// vertex lighting mode for better performance
+extern	cvar_t	*r_uiFullScreen;				// ui is running fullscreen
+
+extern	cvar_t	*r_logFile;						// number of frames to emit GL logs
+extern	cvar_t	*r_showtris;					// enables wireframe rendering of the world
+extern	cvar_t	*r_showsky;						// forces sky in front of all surfaces
+extern	cvar_t	*r_shownormals;					// draws wireframe normals
+extern	cvar_t	*r_clear;						// force screen clear every frame
+
+extern	cvar_t	*r_shadows;						// controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection
+extern	cvar_t	*r_flares;						// light flares
+
+extern	cvar_t	*r_intensity;
+
+extern	cvar_t	*r_lockpvs;
+extern	cvar_t	*r_noportals;
+extern	cvar_t	*r_portalOnly;
+
+extern	cvar_t	*r_subdivisions;
+extern	cvar_t	*r_lodCurveError;
+extern	cvar_t	*r_smp;
+extern	cvar_t	*r_showSmp;
+extern	cvar_t	*r_skipBackEnd;
+
+extern	cvar_t	*r_stereoEnabled;
+extern	cvar_t	*r_anaglyphMode;
+
+extern  cvar_t  *r_mergeMultidraws;
+extern  cvar_t  *r_mergeLeafSurfaces;
+
+extern  cvar_t  *r_hdr;
+extern  cvar_t  *r_postProcess;
+
+extern  cvar_t  *r_toneMap;
+extern  cvar_t  *r_forceToneMap;
+extern  cvar_t  *r_forceToneMapMin;
+extern  cvar_t  *r_forceToneMapAvg;
+extern  cvar_t  *r_forceToneMapMax;
+
+extern  cvar_t  *r_autoExposure;
+extern  cvar_t  *r_forceAutoExposure;
+extern  cvar_t  *r_forceAutoExposureMin;
+extern  cvar_t  *r_forceAutoExposureMax;
+
+extern  cvar_t  *r_cameraExposure;
+
+extern  cvar_t  *r_srgb;
+
+extern  cvar_t  *r_depthPrepass;
+extern  cvar_t  *r_ssao;
+
+extern  cvar_t  *r_normalMapping;
+extern  cvar_t  *r_specularMapping;
+extern  cvar_t  *r_deluxeMapping;
+extern  cvar_t  *r_parallaxMapping;
+extern  cvar_t  *r_normalAmbient;
+extern  cvar_t  *r_dlightMode;
+extern  cvar_t  *r_pshadowDist;
+extern  cvar_t  *r_recalcMD3Normals;
+extern  cvar_t  *r_mergeLightmaps;
+extern  cvar_t  *r_imageUpsample;
+extern  cvar_t  *r_imageUpsampleMaxSize;
+extern  cvar_t  *r_imageUpsampleType;
+extern  cvar_t  *r_genNormalMaps;
+extern  cvar_t  *r_forceSun;
+extern  cvar_t  *r_forceSunMapLightScale;
+extern  cvar_t  *r_forceSunLightScale;
+extern  cvar_t  *r_forceSunAmbientScale;
+extern  cvar_t  *r_sunShadows;
+extern  cvar_t  *r_shadowFilter;
+extern  cvar_t  *r_shadowMapSize;
+extern  cvar_t  *r_shadowCascadeZNear;
+extern  cvar_t  *r_shadowCascadeZFar;
+extern  cvar_t  *r_shadowCascadeZBias;
+
+extern	cvar_t	*r_greyscale;
+
+extern	cvar_t	*r_ignoreGLErrors;
+
+extern	cvar_t	*r_overBrightBits;
+extern	cvar_t	*r_mapOverBrightBits;
+
+extern	cvar_t	*r_debugSurface;
+extern	cvar_t	*r_simpleMipMaps;
+
+extern	cvar_t	*r_showImages;
+extern	cvar_t	*r_debugSort;
+
+extern	cvar_t	*r_printShaders;
+extern	cvar_t	*r_saveFontData;
+
+extern cvar_t	*r_marksOnTriangleMeshes;
+
+//====================================================================
+
+float R_NoiseGet4f( float x, float y, float z, float t );
+void  R_NoiseInit( void );
+
+void R_SwapBuffers( int );
+
+void R_RenderView( viewParms_t *parms );
+void R_RenderDlightCubemaps(const refdef_t *fd);
+void R_RenderPshadowMaps(const refdef_t *fd);
+void R_RenderSunShadowMaps(const refdef_t *fd, int level);
+
+void R_AddMD3Surfaces( trRefEntity_t *e );
+void R_AddNullModelSurfaces( trRefEntity_t *e );
+void R_AddBeamSurfaces( trRefEntity_t *e );
+void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater );
+void R_AddLightningBoltSurfaces( trRefEntity_t *e );
+
+void R_AddPolygonSurfaces( void );
+
+void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, 
+					 int *fogNum, int *dlightMap, int *pshadowMap );
+
+void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, 
+				   int fogIndex, int dlightMap, int pshadowMap );
+
+void R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, vec3_t normal,
+                        const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2);
+qboolean R_CalcTangentVectors(srfVert_t * dv[3]);
+void R_CalcSurfaceTriangleNeighbors(int numTriangles, srfTriangle_t * triangles);
+void R_CalcSurfaceTrianglePlanes(int numTriangles, srfTriangle_t * triangles, srfVert_t * verts);
+
+#define	CULL_IN		0		// completely unclipped
+#define	CULL_CLIP	1		// clipped by one or more planes
+#define	CULL_OUT	2		// completely outside the clipping planes
+void R_LocalNormalToWorld (const vec3_t local, vec3_t world);
+void R_LocalPointToWorld (const vec3_t local, vec3_t world);
+int R_CullBox (vec3_t bounds[2]);
+int R_CullLocalBox (vec3_t bounds[2]);
+int R_CullPointAndRadiusEx( const vec3_t origin, float radius, const cplane_t* frustum, int numPlanes );
+int R_CullPointAndRadius( const vec3_t origin, float radius );
+int R_CullLocalPointAndRadius( const vec3_t origin, float radius );
+
+void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, qboolean computeFrustum);
+void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *or );
+
+/*
+** GL wrapper/helper functions
+*/
+void	GL_Bind( image_t *image );
+void	GL_BindCubemap( image_t *image );
+void	GL_BindToTMU( image_t *image, int tmu );
+void	GL_SetDefaultState (void);
+void	GL_SelectTexture( int unit );
+void	GL_TextureMode( const char *string );
+void	GL_CheckErrs( char *file, int line );
+#define GL_CheckErrors(...) GL_CheckErrs(__FILE__, __LINE__)
+void	GL_State( unsigned long stateVector );
+void    GL_SetProjectionMatrix(matrix_t matrix);
+void    GL_SetModelviewMatrix(matrix_t matrix);
+void	GL_TexEnv( int env );
+void	GL_Cull( int cullType );
+
+#define GLS_SRCBLEND_ZERO						0x00000001
+#define GLS_SRCBLEND_ONE						0x00000002
+#define GLS_SRCBLEND_DST_COLOR					0x00000003
+#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR		0x00000004
+#define GLS_SRCBLEND_SRC_ALPHA					0x00000005
+#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA		0x00000006
+#define GLS_SRCBLEND_DST_ALPHA					0x00000007
+#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA		0x00000008
+#define GLS_SRCBLEND_ALPHA_SATURATE				0x00000009
+#define		GLS_SRCBLEND_BITS					0x0000000f
+
+#define GLS_DSTBLEND_ZERO						0x00000010
+#define GLS_DSTBLEND_ONE						0x00000020
+#define GLS_DSTBLEND_SRC_COLOR					0x00000030
+#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR		0x00000040
+#define GLS_DSTBLEND_SRC_ALPHA					0x00000050
+#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA		0x00000060
+#define GLS_DSTBLEND_DST_ALPHA					0x00000070
+#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA		0x00000080
+#define		GLS_DSTBLEND_BITS					0x000000f0
+
+#define GLS_DEPTHMASK_TRUE						0x00000100
+
+#define GLS_POLYMODE_LINE						0x00001000
+
+#define GLS_DEPTHTEST_DISABLE					0x00010000
+#define GLS_DEPTHFUNC_EQUAL						0x00020000
+#define GLS_DEPTHFUNC_GREATER                   0x00040000
+#define GLS_DEPTHFUNC_BITS                      0x00060000
+
+#define GLS_ATEST_GT_0							0x10000000
+#define GLS_ATEST_LT_80							0x20000000
+#define GLS_ATEST_GE_80							0x40000000
+#define		GLS_ATEST_BITS						0x70000000
+
+#define GLS_DEFAULT			GLS_DEPTHMASK_TRUE
+
+void	RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+void	RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+
+void		RE_BeginFrame( stereoFrame_t stereoFrame );
+void		RE_BeginRegistration( glconfig_t *glconfig );
+void		RE_LoadWorldMap( const char *mapname );
+void		RE_SetWorldVisData( const byte *vis );
+qhandle_t	RE_RegisterModel( const char *name );
+qhandle_t	RE_RegisterSkin( const char *name );
+void		RE_Shutdown( qboolean destroyWindow );
+
+qboolean	R_GetEntityToken( char *buffer, int size );
+
+model_t		*R_AllocModel( void );
+
+void    	R_Init( void );
+image_t     *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags );
+image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, imgFlags_t flags, int internalFormat );
+void		R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height );
+qboolean	R_GetModeInfo( int *width, int *height, float *windowAspect, int mode );
+
+void		R_SetColorMappings( void );
+void		R_GammaCorrect( byte *buffer, int bufSize );
+
+void	R_ImageList_f( void );
+void	R_SkinList_f( void );
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516
+const void *RB_TakeScreenshotCmd( const void *data );
+void	R_ScreenShot_f( void );
+
+void	R_InitFogTable( void );
+float	R_FogFactor( float s, float t );
+void	R_InitImages( void );
+void	R_DeleteTextures( void );
+int		R_SumOfUsedImages( void );
+void	R_InitSkins( void );
+skin_t	*R_GetSkinByHandle( qhandle_t hSkin );
+
+int R_ComputeLOD( trRefEntity_t *ent );
+
+const void *RB_TakeVideoFrameCmd( const void *data );
+
+//
+// tr_shader.c
+//
+qhandle_t		 RE_RegisterShaderLightMap( const char *name, int lightmapIndex );
+qhandle_t		 RE_RegisterShader( const char *name );
+qhandle_t		 RE_RegisterShaderNoMip( const char *name );
+qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage);
+
+shader_t	*R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage );
+shader_t	*R_GetShaderByHandle( qhandle_t hShader );
+shader_t	*R_GetShaderByState( int index, long *cycleTime );
+shader_t *R_FindShaderByName( const char *name );
+void		R_InitShaders( void );
+void		R_ShaderList_f( void );
+void    R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset);
+
+/*
+====================================================================
+
+IMPLEMENTATION SPECIFIC FUNCTIONS
+
+====================================================================
+*/
+
+void		GLimp_Init( void );
+void		GLimp_Shutdown( void );
+void		GLimp_EndFrame( void );
+
+qboolean	GLimp_SpawnRenderThread( void (*function)( void ) );
+void		*GLimp_RendererSleep( void );
+void		GLimp_FrontEndSleep( void );
+void		GLimp_WakeRenderer( void *data );
+
+void		GLimp_LogComment( char *comment );
+void		GLimp_Minimize(void);
+
+// NOTE TTimo linux works with float gamma value, not the gamma table
+//   the params won't be used, getting the r_gamma cvar directly
+void		GLimp_SetGamma( unsigned char red[256], 
+						    unsigned char green[256],
+							unsigned char blue[256] );
+
+
+void		GLimp_InitExtraExtensions( void );
+/*
+====================================================================
+
+TESSELATOR/SHADER DECLARATIONS
+
+====================================================================
+*/
+
+typedef struct stageVars
+{
+	color4ub_t	colors[SHADER_MAX_VERTEXES];
+	vec2_t		texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES];
+} stageVars_t;
+
+#define MAX_MULTIDRAW_PRIMITIVES	16384
+
+typedef struct shaderCommands_s 
+{
+	glIndex_t	indexes[SHADER_MAX_INDEXES] QALIGN(16);
+	vec4_t		xyz[SHADER_MAX_VERTEXES] QALIGN(16);
+	vec4_t		normal[SHADER_MAX_VERTEXES] QALIGN(16);
+#ifdef USE_VERT_TANGENT_SPACE
+	vec4_t		tangent[SHADER_MAX_VERTEXES] QALIGN(16);
+	vec4_t		bitangent[SHADER_MAX_VERTEXES] QALIGN(16);
+#endif
+	vec2_t		texCoords[SHADER_MAX_VERTEXES][2] QALIGN(16);
+	vec4_t		vertexColors[SHADER_MAX_VERTEXES] QALIGN(16);
+	vec4_t      lightdir[SHADER_MAX_VERTEXES] QALIGN(16);
+	//int			vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16);
+
+	VBO_t       *vbo;
+	IBO_t       *ibo;
+	qboolean    useInternalVBO;
+
+	stageVars_t	svars QALIGN(16);
+
+	//color4ub_t	constantColor255[SHADER_MAX_VERTEXES] QALIGN(16);
+
+	shader_t	*shader;
+	float		shaderTime;
+	int			fogNum;
+
+	int			dlightBits;	// or together of all vertexDlightBits
+	int         pshadowBits;
+
+	int			firstIndex;
+	int			numIndexes;
+	int			numVertexes;
+
+	int         multiDrawPrimitives;
+	GLsizei     multiDrawNumIndexes[MAX_MULTIDRAW_PRIMITIVES];
+	GLvoid *    multiDrawFirstIndex[MAX_MULTIDRAW_PRIMITIVES];
+	GLvoid *    multiDrawLastIndex[MAX_MULTIDRAW_PRIMITIVES];
+
+	// info extracted from current shader
+	int			numPasses;
+	void		(*currentStageIteratorFunc)( void );
+	shaderStage_t	**xstages;
+} shaderCommands_t;
+
+extern	shaderCommands_t	tess;
+
+void RB_BeginSurface(shader_t *shader, int fogNum );
+void RB_EndSurface(void);
+void RB_CheckOverflow( int verts, int indexes );
+#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);}
+
+void R_DrawElementsVBO( int numIndexes, int firstIndex );
+void RB_StageIteratorGeneric( void );
+void RB_StageIteratorSky( void );
+void RB_StageIteratorVertexLitTexture( void );
+void RB_StageIteratorLightmappedMultitexture( void );
+
+void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] );
+void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], float s1, float t1, float s2, float t2 );
+void RB_InstantQuad( vec4_t quadVerts[4] );
+//void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4], vec4_t color, shaderProgram_t *sp, vec2_t invTexRes);
+void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]);
+
+void RB_ShowImages( void );
+
+
+/*
+============================================================
+
+WORLD MAP
+
+============================================================
+*/
+
+void R_AddBrushModelSurfaces( trRefEntity_t *e );
+void R_AddWorldSurfaces( void );
+qboolean R_inPVS( const vec3_t p1, const vec3_t p2 );
+
+
+/*
+============================================================
+
+FLARES
+
+============================================================
+*/
+
+void R_ClearFlares( void );
+
+void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal );
+void RB_AddDlightFlares( void );
+void RB_RenderFlares (void);
+
+/*
+============================================================
+
+LIGHTS
+
+============================================================
+*/
+
+void R_DlightBmodel( bmodel_t *bmodel );
+void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent );
+void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or );
+int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+int R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world );
+
+
+/*
+============================================================
+
+SHADOWS
+
+============================================================
+*/
+
+void RB_ShadowTessEnd( void );
+void RB_ShadowFinish( void );
+void RB_ProjectionShadowDeform( void );
+
+/*
+============================================================
+
+SKIES
+
+============================================================
+*/
+
+void R_BuildCloudData( shaderCommands_t *shader );
+void R_InitSkyTexCoords( float cloudLayerHeight );
+void R_DrawSkyBox( shaderCommands_t *shader );
+void RB_DrawSun( void );
+void RB_ClipSkyPolygons( shaderCommands_t *shader );
+
+/*
+============================================================
+
+CURVE TESSELATION
+
+============================================================
+*/
+
+#define PATCH_STITCHING
+
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+								srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] );
+srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror );
+srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror );
+void R_FreeSurfaceGridMesh( srfGridMesh_t *grid );
+
+/*
+============================================================
+
+MARKERS, POLYGON PROJECTION ON WORLD POLYGONS
+
+============================================================
+*/
+
+int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
+				   int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
+
+
+/*
+============================================================
+
+VERTEX BUFFER OBJECTS
+
+============================================================
+*/
+VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage);
+VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * vertexes, uint32_t stateBits, vboUsage_t usage);
+
+IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage);
+IBO_t          *R_CreateIBO2(const char *name, int numTriangles, srfTriangle_t * triangles, vboUsage_t usage);
+
+void            R_BindVBO(VBO_t * vbo);
+void            R_BindNullVBO(void);
+
+void            R_BindIBO(IBO_t * ibo);
+void            R_BindNullIBO(void);
+
+void            R_InitVBOs(void);
+void            R_ShutdownVBOs(void);
+void            R_VBOList_f(void);
+
+void            RB_UpdateVBOs(unsigned int attribBits);
+
+
+/*
+============================================================
+
+GLSL
+
+============================================================
+*/
+
+void GLSL_InitGPUShaders(void);
+void GLSL_ShutdownGPUShaders(void);
+void GLSL_VertexAttribsState(uint32_t stateBits);
+void GLSL_VertexAttribPointers(uint32_t attribBits);
+void GLSL_BindProgram(shaderProgram_t * program);
+void GLSL_BindNullProgram(void);
+
+void GLSL_SetNumUniforms(shaderProgram_t *program, int numUniforms);
+void GLSL_SetUniformName(shaderProgram_t *program, int uniformNum, const char *name);
+void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value);
+void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value);
+void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v);
+void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v);
+void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v);
+void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v);
+void GLSL_SetUniformMatrix16(shaderProgram_t *program, int uniformNum, const matrix_t matrix);
+
+shaderProgram_t *GLSL_GetGenericShaderProgram(int stage);
+
+/*
+============================================================
+
+SCENE GENERATION
+
+============================================================
+*/
+
+void R_ToggleSmpFrame( void );
+
+void RE_ClearScene( void );
+void RE_AddRefEntityToScene( const refEntity_t *ent );
+void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num );
+void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void RE_RenderScene( const refdef_t *fd );
+
+#ifdef RAVENMD4
+/*
+=============================================================
+
+UNCOMPRESSING BONES
+
+=============================================================
+*/
+
+#define MC_BITS_X (16)
+#define MC_BITS_Y (16)
+#define MC_BITS_Z (16)
+#define MC_BITS_VECT (16)
+
+#define MC_SCALE_X (1.0f/64)
+#define MC_SCALE_Y (1.0f/64)
+#define MC_SCALE_Z (1.0f/64)
+
+void MC_UnCompress(float mat[3][4],const unsigned char * comp);
+#endif
+
+/*
+=============================================================
+
+ANIMATED MODELS
+
+=============================================================
+*/
+
+// void R_MakeAnimModel( model_t *model );      haven't seen this one really, so not needed I guess.
+void R_AddAnimSurfaces( trRefEntity_t *ent );
+void RB_SurfaceAnim( md4Surface_t *surfType );
+#ifdef RAVENMD4
+void R_MDRAddAnimSurfaces( trRefEntity_t *ent );
+void RB_MDRSurfaceAnim( md4Surface_t *surface );
+#endif
+qboolean R_LoadIQM (model_t *mod, void *buffer, int filesize, const char *name );
+void R_AddIQMSurfaces( trRefEntity_t *ent );
+void RB_IQMSurfaceAnim( surfaceType_t *surface );
+int R_IQMLerpTag( orientation_t *tag, iqmData_t *data,
+                  int startFrame, int endFrame,
+                  float frac, const char *tagName );
+
+/*
+=============================================================
+
+IMAGE LOADERS
+
+=============================================================
+*/
+
+void R_LoadBMP( const char *name, byte **pic, int *width, int *height );
+void R_LoadJPG( const char *name, byte **pic, int *width, int *height );
+void R_LoadPCX( const char *name, byte **pic, int *width, int *height );
+void R_LoadPNG( const char *name, byte **pic, int *width, int *height );
+void R_LoadTGA( const char *name, byte **pic, int *width, int *height );
+
+/*
+=============================================================
+=============================================================
+*/
+void	R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
+							vec4_t eye, vec4_t dst );
+void	R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window );
+
+void	RB_DeformTessGeometry( void );
+
+void	RB_CalcEnvironmentTexCoords( float *dstTexCoords );
+void	RB_CalcFogTexCoords( float *dstTexCoords );
+void	RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords );
+void	RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords );
+void	RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords );
+void	RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords );
+void	RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords );
+
+void	RB_CalcScaleTexMatrix( const float scale[2], float *matrix );
+void	RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix );
+void	RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix );
+void RB_CalcTurbulentTexMatrix( const waveForm_t *wf, matrix_t matrix );
+void	RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix  );
+void	RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix );
+
+void	RB_CalcModulateColorsByFog( unsigned char *dstColors );
+void	RB_CalcModulateAlphasByFog( unsigned char *dstColors );
+void	RB_CalcModulateRGBAsByFog( unsigned char *dstColors );
+void	RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors );
+float	RB_CalcWaveAlphaSingle( const waveForm_t *wf );
+void	RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors );
+float	RB_CalcWaveColorSingle( const waveForm_t *wf );
+void	RB_CalcAlphaFromEntity( unsigned char *dstColors );
+void	RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors );
+void	RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords );
+void	RB_CalcColorFromEntity( unsigned char *dstColors );
+void	RB_CalcColorFromOneMinusEntity( unsigned char *dstColors );
+void	RB_CalcSpecularAlpha( unsigned char *alphas );
+void	RB_CalcDiffuseColor( unsigned char *colors );
+
+/*
+=============================================================
+
+RENDERER BACK END FUNCTIONS
+
+=============================================================
+*/
+
+void RB_RenderThread( void );
+void RB_ExecuteRenderCommands( const void *data );
+
+/*
+=============================================================
+
+RENDERER BACK END COMMAND QUEUE
+
+=============================================================
+*/
+
+#define	MAX_RENDER_COMMANDS	0x40000
+
+typedef struct {
+	byte	cmds[MAX_RENDER_COMMANDS];
+	int		used;
+} renderCommandList_t;
+
+typedef struct {
+	int		commandId;
+	float	color[4];
+} setColorCommand_t;
+
+typedef struct {
+	int		commandId;
+	int		buffer;
+} drawBufferCommand_t;
+
+typedef struct {
+	int		commandId;
+	image_t	*image;
+	int		width;
+	int		height;
+	void	*data;
+} subImageCommand_t;
+
+typedef struct {
+	int		commandId;
+} swapBuffersCommand_t;
+
+typedef struct {
+	int		commandId;
+	int		buffer;
+} endFrameCommand_t;
+
+typedef struct {
+	int		commandId;
+	shader_t	*shader;
+	float	x, y;
+	float	w, h;
+	float	s1, t1;
+	float	s2, t2;
+} stretchPicCommand_t;
+
+typedef struct {
+	int		commandId;
+	trRefdef_t	refdef;
+	viewParms_t	viewParms;
+	drawSurf_t *drawSurfs;
+	int		numDrawSurfs;
+} drawSurfsCommand_t;
+
+typedef struct {
+	int commandId;
+	int x;
+	int y;
+	int width;
+	int height;
+	char *fileName;
+	qboolean jpeg;
+} screenshotCommand_t;
+
+typedef struct {
+	int						commandId;
+	int						width;
+	int						height;
+	byte					*captureBuffer;
+	byte					*encodeBuffer;
+	qboolean			motionJpeg;
+} videoFrameCommand_t;
+
+typedef struct
+{
+	int commandId;
+
+	GLboolean rgba[4];
+} colorMaskCommand_t;
+
+typedef struct
+{
+	int commandId;
+} clearDepthCommand_t;
+
+typedef struct {
+	int commandId;
+	int map;
+	int cubeSide;
+} capShadowmapCommand_t;
+
+typedef struct {
+	int		commandId;
+	trRefdef_t	refdef;
+	viewParms_t	viewParms;
+} postProcessCommand_t;
+
+typedef enum {
+	RC_END_OF_LIST,
+	RC_SET_COLOR,
+	RC_STRETCH_PIC,
+	RC_DRAW_SURFS,
+	RC_DRAW_BUFFER,
+	RC_SWAP_BUFFERS,
+	RC_SCREENSHOT,
+	RC_VIDEOFRAME,
+	RC_COLORMASK,
+	RC_CLEARDEPTH,
+	RC_CAPSHADOWMAP,
+	RC_POSTPROCESS
+} renderCommand_t;
+
+
+// these are sort of arbitrary limits.
+// the limits apply to the sum of all scenes in a frame --
+// the main view, all the 3D icons, etc
+#define	MAX_POLYS		600
+#define	MAX_POLYVERTS	3000
+
+// all of the information needed by the back end must be
+// contained in a backEndData_t.  This entire structure is
+// duplicated so the front and back end can run in parallel
+// on an SMP machine
+typedef struct {
+	drawSurf_t	drawSurfs[MAX_DRAWSURFS];
+	dlight_t	dlights[MAX_DLIGHTS];
+	trRefEntity_t	entities[MAX_REFENTITIES];
+	srfPoly_t	*polys;//[MAX_POLYS];
+	polyVert_t	*polyVerts;//[MAX_POLYVERTS];
+	pshadow_t pshadows[MAX_CALC_PSHADOWS];
+	renderCommandList_t	commands;
+} backEndData_t;
+
+extern	int		max_polys;
+extern	int		max_polyverts;
+
+extern	backEndData_t	*backEndData[SMP_FRAMES];	// the second one may not be allocated
+
+extern	volatile renderCommandList_t	*renderCommandList;
+
+extern	volatile qboolean	renderThreadActive;
+
+
+void *R_GetCommandBuffer( int bytes );
+void RB_ExecuteRenderCommands( const void *data );
+
+void R_InitCommandBuffers( void );
+void R_ShutdownCommandBuffers( void );
+
+void R_SyncRenderThread( void );
+
+void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs );
+void R_AddCapShadowmapCmd( int dlight, int cubeSide );
+void R_AddPostProcessCmd (void);
+
+void RE_SetColor( const float *rgba );
+void RE_StretchPic ( float x, float y, float w, float h, 
+					  float s1, float t1, float s2, float t2, qhandle_t hShader );
+void RE_BeginFrame( stereoFrame_t stereoFrame );
+void RE_EndFrame( int *frontEndMsec, int *backEndMsec );
+void RE_SaveJPG(char * filename, int quality, int image_width, int image_height,
+                unsigned char *image_buffer, int padding);
+size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,
+		          int image_width, int image_height, byte *image_buffer, int padding);
+void RE_TakeVideoFrame( int width, int height,
+		byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
+
+// font stuff
+void R_InitFreeType( void );
+void R_DoneFreeType( void );
+void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font);
+
+
+#endif //TR_LOCAL_H

Added: trunk/code/rend2/tr_main.c
===================================================================
--- trunk/code/rend2/tr_main.c	                        (rev 0)
+++ trunk/code/rend2/tr_main.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,2878 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_main.c -- main control flow for each frame
+
+#include "tr_local.h"
+
+#include <string.h> // memcpy
+
+trGlobals_t		tr;
+
+static float	s_flipMatrix[16] = {
+	// convert from our coordinate system (looking down X)
+	// to OpenGL's coordinate system (looking down -Z)
+	0, 0, -1, 0,
+	-1, 0, 0, 0,
+	0, 1, 0, 0,
+	0, 0, 0, 1
+};
+
+
+refimport_t	ri;
+
+// entities that will have procedurally generated surfaces will just
+// point at this for their sorting surface
+surfaceType_t	entitySurface = SF_ENTITY;
+
+/*
+================
+R_CompareVert
+================
+*/
+qboolean R_CompareVert(srfVert_t * v1, srfVert_t * v2, qboolean checkST)
+{
+	int             i;
+
+	for(i = 0; i < 3; i++)
+	{
+		if(floor(v1->xyz[i] + 0.1) != floor(v2->xyz[i] + 0.1))
+		{
+			return qfalse;
+		}
+
+		if(checkST && ((v1->st[0] != v2->st[0]) || (v1->st[1] != v2->st[1])))
+		{
+			return qfalse;
+		}
+	}
+
+	return qtrue;
+}
+
+/*
+=============
+R_CalcNormalForTriangle
+=============
+*/
+void R_CalcNormalForTriangle(vec3_t normal, const vec3_t v0, const vec3_t v1, const vec3_t v2)
+{
+	vec3_t          udir, vdir;
+
+	// compute the face normal based on vertex points
+	VectorSubtract(v2, v0, udir);
+	VectorSubtract(v1, v0, vdir);
+	CrossProduct(udir, vdir, normal);
+
+	VectorNormalize(normal);
+}
+
+/*
+=============
+R_CalcTangentsForTriangle
+http://members.rogers.com/deseric/tangentspace.htm
+=============
+*/
+void R_CalcTangentsForTriangle(vec3_t tangent, vec3_t bitangent,
+							   const vec3_t v0, const vec3_t v1, const vec3_t v2,
+							   const vec2_t t0, const vec2_t t1, const vec2_t t2)
+{
+	int             i;
+	vec3_t          planes[3];
+	vec3_t          u, v;
+
+	for(i = 0; i < 3; i++)
+	{
+		VectorSet(u, v1[i] - v0[i], t1[0] - t0[0], t1[1] - t0[1]);
+		VectorSet(v, v2[i] - v0[i], t2[0] - t0[0], t2[1] - t0[1]);
+
+		VectorNormalize(u);
+		VectorNormalize(v);
+
+		CrossProduct(u, v, planes[i]);
+	}
+
+	//So your tangent space will be defined by this :
+	//Normal = Normal of the triangle or Tangent X Bitangent (careful with the cross product,
+	// you have to make sure the normal points in the right direction)
+	//Tangent = ( dp(Fx(s,t)) / ds,  dp(Fy(s,t)) / ds, dp(Fz(s,t)) / ds )   or     ( -Bx/Ax, -By/Ay, - Bz/Az )
+	//Bitangent =  ( dp(Fx(s,t)) / dt,  dp(Fy(s,t)) / dt, dp(Fz(s,t)) / dt )  or     ( -Cx/Ax, -Cy/Ay, -Cz/Az )
+
+	// tangent...
+	tangent[0] = -planes[0][1] / planes[0][0];
+	tangent[1] = -planes[1][1] / planes[1][0];
+	tangent[2] = -planes[2][1] / planes[2][0];
+	VectorNormalize(tangent);
+
+	// bitangent...
+	bitangent[0] = -planes[0][2] / planes[0][0];
+	bitangent[1] = -planes[1][2] / planes[1][0];
+	bitangent[2] = -planes[2][2] / planes[2][0];
+	VectorNormalize(bitangent);
+}
+
+
+
+
+/*
+=============
+R_CalcTangentSpace
+=============
+*/
+void R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, vec3_t normal,
+						const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2)
+{
+	vec3_t          cp, u, v;
+	vec3_t          faceNormal;
+
+	VectorSet(u, v1[0] - v0[0], t1[0] - t0[0], t1[1] - t0[1]);
+	VectorSet(v, v2[0] - v0[0], t2[0] - t0[0], t2[1] - t0[1]);
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[0] = -cp[1] / cp[0];
+		bitangent[0] = -cp[2] / cp[0];
+	}
+
+	u[0] = v1[1] - v0[1];
+	v[0] = v2[1] - v0[1];
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[1] = -cp[1] / cp[0];
+		bitangent[1] = -cp[2] / cp[0];
+	}
+
+	u[0] = v1[2] - v0[2];
+	v[0] = v2[2] - v0[2];
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[2] = -cp[1] / cp[0];
+		bitangent[2] = -cp[2] / cp[0];
+	}
+
+	VectorNormalize(tangent);
+	VectorNormalize(bitangent);
+
+	// compute the face normal based on vertex points
+	if ( normal[0] == 0.0f && normal[1] == 0.0f && normal[2] == 0.0f )
+	{
+		VectorSubtract(v2, v0, u);
+		VectorSubtract(v1, v0, v);
+		CrossProduct(u, v, faceNormal);
+	}
+	else
+	{
+		VectorCopy(normal, faceNormal);
+	}
+
+	VectorNormalize(faceNormal);
+
+#if 1
+	// Gram-Schmidt orthogonalize
+	//tangent[a] = (t - n * Dot(n, t)).Normalize();
+	VectorMA(tangent, -DotProduct(faceNormal, tangent), faceNormal, tangent);
+	VectorNormalize(tangent);
+
+	// compute the cross product B=NxT
+	//CrossProduct(normal, tangent, bitangent);
+#else
+	// normal, compute the cross product N=TxB
+	CrossProduct(tangent, bitangent, normal);
+	VectorNormalize(normal);
+
+	if(DotProduct(normal, faceNormal) < 0)
+	{
+		//VectorInverse(normal);
+		//VectorInverse(tangent);
+		//VectorInverse(bitangent);
+
+		// compute the cross product T=BxN
+		CrossProduct(bitangent, faceNormal, tangent);
+
+		// compute the cross product B=NxT
+		//CrossProduct(normal, tangent, bitangent);
+	}
+#endif
+
+	VectorCopy(faceNormal, normal);
+}
+
+void R_CalcTangentSpaceFast(vec3_t tangent, vec3_t bitangent, vec3_t normal,
+						const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2)
+{
+	vec3_t          cp, u, v;
+	vec3_t          faceNormal;
+
+	VectorSet(u, v1[0] - v0[0], t1[0] - t0[0], t1[1] - t0[1]);
+	VectorSet(v, v2[0] - v0[0], t2[0] - t0[0], t2[1] - t0[1]);
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[0] = -cp[1] / cp[0];
+		bitangent[0] = -cp[2] / cp[0];
+	}
+
+	u[0] = v1[1] - v0[1];
+	v[0] = v2[1] - v0[1];
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[1] = -cp[1] / cp[0];
+		bitangent[1] = -cp[2] / cp[0];
+	}
+
+	u[0] = v1[2] - v0[2];
+	v[0] = v2[2] - v0[2];
+
+	CrossProduct(u, v, cp);
+	if(fabs(cp[0]) > 10e-6)
+	{
+		tangent[2] = -cp[1] / cp[0];
+		bitangent[2] = -cp[2] / cp[0];
+	}
+
+	VectorNormalizeFast(tangent);
+	VectorNormalizeFast(bitangent);
+
+	// compute the face normal based on vertex points
+	VectorSubtract(v2, v0, u);
+	VectorSubtract(v1, v0, v);
+	CrossProduct(u, v, faceNormal);
+
+	VectorNormalizeFast(faceNormal);
+
+#if 0
+	// normal, compute the cross product N=TxB
+	CrossProduct(tangent, bitangent, normal);
+	VectorNormalizeFast(normal);
+
+	if(DotProduct(normal, faceNormal) < 0)
+	{
+		VectorInverse(normal);
+		//VectorInverse(tangent);
+		//VectorInverse(bitangent);
+
+		CrossProduct(normal, tangent, bitangent);
+	}
+
+	VectorCopy(faceNormal, normal);
+#else
+	// Gram-Schmidt orthogonalize
+		//tangent[a] = (t - n * Dot(n, t)).Normalize();
+	VectorMA(tangent, -DotProduct(faceNormal, tangent), faceNormal, tangent);
+	VectorNormalizeFast(tangent);
+#endif
+
+	VectorCopy(faceNormal, normal);
+}
+
+/*
+http://www.terathon.com/code/tangent.html
+*/
+void R_CalcTBN(vec3_t tangent, vec3_t bitangent, vec3_t normal,
+						const vec3_t v1, const vec3_t v2, const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3)
+{
+	vec3_t          u, v;
+	float			x1, x2, y1, y2, z1, z2;
+	float			s1, s2, t1, t2;
+	float			r, dot;
+
+	x1 = v2[0] - v1[0];
+	x2 = v3[0] - v1[0];
+	y1 = v2[1] - v1[1];
+	y2 = v3[1] - v1[1];
+	z1 = v2[2] - v1[2];
+	z2 = v3[2] - v1[2];
+
+	s1 = w2[0] - w1[0];
+	s2 = w3[0] - w1[0];
+	t1 = w2[1] - w1[1];
+	t2 = w3[1] - w1[1];
+
+	r = 1.0f / (s1 * t2 - s2 * t1);
+
+	VectorSet(tangent, (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
+	VectorSet(bitangent, (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
+
+	// compute the face normal based on vertex points
+	VectorSubtract(v3, v1, u);
+	VectorSubtract(v2, v1, v);
+	CrossProduct(u, v, normal);
+
+	VectorNormalize(normal);
+
+	// Gram-Schmidt orthogonalize
+	//tangent[a] = (t - n * Dot(n, t)).Normalize();
+	dot = DotProduct(normal, tangent);
+	VectorMA(tangent, -dot, normal, tangent);
+	VectorNormalize(tangent);
+
+	// B=NxT
+	//CrossProduct(normal, tangent, bitangent);
+}
+
+void R_CalcTBN2(vec3_t tangent, vec3_t bitangent, vec3_t normal,
+						const vec3_t v1, const vec3_t v2, const vec3_t v3, const vec2_t t1, const vec2_t t2, const vec2_t t3)
+{
+	vec3_t			v2v1;
+	vec3_t			v3v1;
+
+	float			c2c1_T;
+	float			c2c1_B;
+
+	float			c3c1_T;
+	float			c3c1_B;
+
+	float			denominator;
+	float			scale1, scale2;
+
+	vec3_t			T, B, N, C;
+
+
+	// Calculate the tangent basis for each vertex of the triangle
+	// UPDATE: In the 3rd edition of the accompanying article, the for-loop located here has
+	// been removed as it was redundant (the entire TBN matrix was calculated three times
+	// instead of just one).
+	//
+	// Please note, that this function relies on the fact that the input geometry are triangles
+	// and the tangent basis for each vertex thus is identical!
+	//
+
+	// Calculate the vectors from the current vertex to the two other vertices in the triangle
+	VectorSubtract(v2, v1, v2v1);
+	VectorSubtract(v3, v1, v3v1);
+
+	// The equation presented in the article states that:
+	// c2c1_T = V2.texcoord.x – V1.texcoord.x
+	// c2c1_B = V2.texcoord.y – V1.texcoord.y
+	// c3c1_T = V3.texcoord.x – V1.texcoord.x
+	// c3c1_B = V3.texcoord.y – V1.texcoord.y
+
+	// Calculate c2c1_T and c2c1_B
+	c2c1_T = t2[0] - t1[0];
+	c2c1_B = t2[1] - t2[1];
+
+	// Calculate c3c1_T and c3c1_B
+	c3c1_T = t3[0] - t1[0];
+	c3c1_B = t3[1] - t1[1];
+
+	denominator = c2c1_T * c3c1_B - c3c1_T * c2c1_B;
+	//if(ROUNDOFF(fDenominator) == 0.0f)
+	if(denominator == 0.0f)
+	{
+		// We won't risk a divide by zero, so set the tangent matrix to the identity matrix
+		VectorSet(tangent, 1, 0, 0);
+		VectorSet(bitangent, 0, 1, 0);
+		VectorSet(normal, 0, 0, 1);
+	}
+	else
+	{
+		// Calculate the reciprocal value once and for all (to achieve speed)
+		scale1 = 1.0f / denominator;
+
+		// T and B are calculated just as the equation in the article states
+		VectorSet(T, (c3c1_B * v2v1[0] - c2c1_B * v3v1[0]) * scale1,
+					 (c3c1_B * v2v1[1] - c2c1_B * v3v1[1]) * scale1,
+					 (c3c1_B * v2v1[2] - c2c1_B * v3v1[2]) * scale1);
+
+		VectorSet(B, (-c3c1_T * v2v1[0] + c2c1_T * v3v1[0]) * scale1,
+					 (-c3c1_T * v2v1[1] + c2c1_T * v3v1[1]) * scale1,
+					 (-c3c1_T * v2v1[2] + c2c1_T * v3v1[2]) * scale1);
+
+		// The normal N is calculated as the cross product between T and B
+		CrossProduct(T, B, N);
+
+#if 0
+		VectorCopy(T, tangent);
+		VectorCopy(B, bitangent);
+		VectorCopy(N, normal);
+#else
+		// Calculate the reciprocal value once and for all (to achieve speed)
+		scale2 = 1.0f / ((T[0] * B[1] * N[2] - T[2] * B[1] * N[0]) +
+					(B[0] * N[1] * T[2] - B[2] * N[1] * T[0]) +
+					(N[0] * T[1] * B[2] - N[2] * T[1] * B[0]));
+
+		// Calculate the inverse if the TBN matrix using the formula described in the article.
+		// We store the basis vectors directly in the provided TBN matrix: pvTBNMatrix
+		CrossProduct(B, N, C); tangent[0] = C[0] * scale2;
+		CrossProduct(N, T, C); tangent[1] = -C[0] * scale2;
+		CrossProduct(T, B, C); tangent[2] = C[0] * scale2;
+		VectorNormalize(tangent);
+
+		CrossProduct(B, N, C); bitangent[0] = -C[1] * scale2;
+		CrossProduct(N, T, C); bitangent[1] = C[1] * scale2;
+		CrossProduct(T, B, C); bitangent[2] = -C[1] * scale2;
+		VectorNormalize(bitangent);
+
+		CrossProduct(B, N, C); normal[0] = C[2] * scale2;
+		CrossProduct(N, T, C); normal[1] = -C[2] * scale2;
+		CrossProduct(T, B, C); normal[2] = C[2] * scale2;
+		VectorNormalize(normal);
+#endif
+	}
+}
+
+
+#ifdef USE_VERT_TANGENT_SPACE
+qboolean R_CalcTangentVectors(srfVert_t * dv[3])
+{
+	int             i;
+	float           bb, s, t;
+	vec3_t          bary;
+
+
+	/* calculate barycentric basis for the triangle */
+	bb = (dv[1]->st[0] - dv[0]->st[0]) * (dv[2]->st[1] - dv[0]->st[1]) - (dv[2]->st[0] - dv[0]->st[0]) * (dv[1]->st[1] - dv[0]->st[1]);
+	if(fabs(bb) < 0.00000001f)
+		return qfalse;
+
+	/* do each vertex */
+	for(i = 0; i < 3; i++)
+	{
+		// calculate s tangent vector
+		s = dv[i]->st[0] + 10.0f;
+		t = dv[i]->st[1];
+		bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb;
+		bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb;
+		bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb;
+
+		dv[i]->tangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
+		dv[i]->tangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
+		dv[i]->tangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];
+
+		VectorSubtract(dv[i]->tangent, dv[i]->xyz, dv[i]->tangent);
+		VectorNormalize(dv[i]->tangent);
+
+		// calculate t tangent vector
+		s = dv[i]->st[0];
+		t = dv[i]->st[1] + 10.0f;
+		bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb;
+		bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb;
+		bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb;
+
+		dv[i]->bitangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
+		dv[i]->bitangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
+		dv[i]->bitangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];
+
+		VectorSubtract(dv[i]->bitangent, dv[i]->xyz, dv[i]->bitangent);
+		VectorNormalize(dv[i]->bitangent);
+
+		// debug code
+		//% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i,
+		//%     stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] );
+	}
+
+	return qtrue;
+}
+#endif
+
+
+/*
+=================
+R_FindSurfaceTriangleWithEdge
+Tr3B - recoded from Q2E
+=================
+*/
+static int R_FindSurfaceTriangleWithEdge(int numTriangles, srfTriangle_t * triangles, int start, int end, int ignore)
+{
+	srfTriangle_t  *tri;
+	int             count, match;
+	int             i;
+
+	count = 0;
+	match = -1;
+
+	for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
+	{
+		if((tri->indexes[0] == start && tri->indexes[1] == end) ||
+		   (tri->indexes[1] == start && tri->indexes[2] == end) || (tri->indexes[2] == start && tri->indexes[0] == end))
+		{
+			if(i != ignore)
+			{
+				match = i;
+			}
+
+			count++;
+		}
+		else if((tri->indexes[1] == start && tri->indexes[0] == end) ||
+				(tri->indexes[2] == start && tri->indexes[1] == end) || (tri->indexes[0] == start && tri->indexes[2] == end))
+		{
+			count++;
+		}
+	}
+
+	// detect edges shared by three triangles and make them seams
+	if(count > 2)
+	{
+		match = -1;
+	}
+
+	return match;
+}
+
+
+/*
+=================
+R_CalcSurfaceTriangleNeighbors
+Tr3B - recoded from Q2E
+=================
+*/
+void R_CalcSurfaceTriangleNeighbors(int numTriangles, srfTriangle_t * triangles)
+{
+	int             i;
+	srfTriangle_t  *tri;
+
+	for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
+	{
+		tri->neighbors[0] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[1], tri->indexes[0], i);
+		tri->neighbors[1] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[2], tri->indexes[1], i);
+		tri->neighbors[2] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[0], tri->indexes[2], i);
+	}
+}
+
+/*
+=================
+R_CalcSurfaceTrianglePlanes
+=================
+*/
+void R_CalcSurfaceTrianglePlanes(int numTriangles, srfTriangle_t * triangles, srfVert_t * verts)
+{
+	int             i;
+	srfTriangle_t  *tri;
+
+	for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
+	{
+		float          *v1, *v2, *v3;
+		vec3_t          d1, d2;
+
+		v1 = verts[tri->indexes[0]].xyz;
+		v2 = verts[tri->indexes[1]].xyz;
+		v3 = verts[tri->indexes[2]].xyz;
+
+		VectorSubtract(v2, v1, d1);
+		VectorSubtract(v3, v1, d2);
+
+		CrossProduct(d2, d1, tri->plane);
+		tri->plane[3] = DotProduct(tri->plane, v1);
+	}
+}
+
+
+/*
+=================
+R_CullLocalBox
+
+Returns CULL_IN, CULL_CLIP, or CULL_OUT
+=================
+*/
+int R_CullLocalBox(vec3_t localBounds[2]) {
+#if 0
+	int		i, j;
+	vec3_t	transformed[8];
+	float	dists[8];
+	vec3_t	v;
+	cplane_t	*frust;
+	int			anyBack;
+	int			front, back;
+
+	if ( r_nocull->integer ) {
+		return CULL_CLIP;
+	}
+
+	// transform into world space
+	for (i = 0 ; i < 8 ; i++) {
+		v[0] = bounds[i&1][0];
+		v[1] = bounds[(i>>1)&1][1];
+		v[2] = bounds[(i>>2)&1][2];
+
+		VectorCopy( tr.or.origin, transformed[i] );
+		VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] );
+		VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] );
+		VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] );
+	}
+
+	// check against frustum planes
+	anyBack = 0;
+	for (i = 0 ; i < 4 ; i++) {
+		frust = &tr.viewParms.frustum[i];
+
+		front = back = 0;
+		for (j = 0 ; j < 8 ; j++) {
+			dists[j] = DotProduct(transformed[j], frust->normal);
+			if ( dists[j] > frust->dist ) {
+				front = 1;
+				if ( back ) {
+					break;		// a point is in front
+				}
+			} else {
+				back = 1;
+			}
+		}
+		if ( !front ) {
+			// all points were behind one of the planes
+			return CULL_OUT;
+		}
+		anyBack |= back;
+	}
+
+	if ( !anyBack ) {
+		return CULL_IN;		// completely inside frustum
+	}
+
+	return CULL_CLIP;		// partially clipped
+#else
+	int             j;
+	vec3_t          transformed;
+	vec3_t          v;
+	vec3_t          worldBounds[2];
+
+	if(r_nocull->integer)
+	{
+		return CULL_CLIP;
+	}
+
+	// transform into world space
+	ClearBounds(worldBounds[0], worldBounds[1]);
+
+	for(j = 0; j < 8; j++)
+	{
+		v[0] = localBounds[j & 1][0];
+		v[1] = localBounds[(j >> 1) & 1][1];
+		v[2] = localBounds[(j >> 2) & 1][2];
+
+		R_LocalPointToWorld(v, transformed);
+
+		AddPointToBounds(transformed, worldBounds[0], worldBounds[1]);
+	}
+
+	return R_CullBox(worldBounds);
+#endif
+}
+
+/*
+=================
+R_CullBox
+
+Returns CULL_IN, CULL_CLIP, or CULL_OUT
+=================
+*/
+int R_CullBox(vec3_t worldBounds[2]) {
+	int             i;
+	cplane_t       *frust;
+	qboolean        anyClip;
+	int             r;
+
+	// check against frustum planes
+	anyClip = qfalse;
+	for(i = 0; i < 4 /*FRUSTUM_PLANES*/; i++)
+	{
+		frust = &tr.viewParms.frustum[i];
+
+		r = BoxOnPlaneSide(worldBounds[0], worldBounds[1], frust);
+
+		if(r == 2)
+		{
+			// completely outside frustum
+			return CULL_OUT;
+		}
+		if(r == 3)
+		{
+			anyClip = qtrue;
+		}
+	}
+
+	if(!anyClip)
+	{
+		// completely inside frustum
+		return CULL_IN;
+	}
+
+	// partially clipped
+	return CULL_CLIP;
+}
+
+/*
+** R_CullLocalPointAndRadius
+*/
+int R_CullLocalPointAndRadius( const vec3_t pt, float radius )
+{
+	vec3_t transformed;
+
+	R_LocalPointToWorld( pt, transformed );
+
+	return R_CullPointAndRadius( transformed, radius );
+}
+
+/*
+** R_CullPointAndRadius
+*/
+int R_CullPointAndRadiusEx( const vec3_t pt, float radius, const cplane_t* frustum, int numPlanes )
+{
+	int		i;
+	float	dist;
+	const cplane_t	*frust;
+	qboolean mightBeClipped = qfalse;
+
+	if ( r_nocull->integer ) {
+		return CULL_CLIP;
+	}
+
+	// check against frustum planes
+	for (i = 0 ; i < numPlanes ; i++) 
+	{
+		frust = &frustum[i];
+
+		dist = DotProduct( pt, frust->normal) - frust->dist;
+		if ( dist < -radius )
+		{
+			return CULL_OUT;
+		}
+		else if ( dist <= radius ) 
+		{
+			mightBeClipped = qtrue;
+		}
+	}
+
+	if ( mightBeClipped )
+	{
+		return CULL_CLIP;
+	}
+
+	return CULL_IN;		// completely inside frustum
+}
+
+/*
+** R_CullPointAndRadius
+*/
+int R_CullPointAndRadius( const vec3_t pt, float radius )
+{
+	return R_CullPointAndRadiusEx(pt, radius, tr.viewParms.frustum, ARRAY_LEN(tr.viewParms.frustum));
+}
+
+/*
+=================
+R_LocalNormalToWorld
+
+=================
+*/
+void R_LocalNormalToWorld (const vec3_t local, vec3_t world) {
+	world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0];
+	world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1];
+	world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2];
+}
+
+/*
+=================
+R_LocalPointToWorld
+
+=================
+*/
+void R_LocalPointToWorld (const vec3_t local, vec3_t world) {
+	world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0];
+	world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1];
+	world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2];
+}
+
+/*
+=================
+R_WorldToLocal
+
+=================
+*/
+void R_WorldToLocal (const vec3_t world, vec3_t local) {
+	local[0] = DotProduct(world, tr.or.axis[0]);
+	local[1] = DotProduct(world, tr.or.axis[1]);
+	local[2] = DotProduct(world, tr.or.axis[2]);
+}
+
+/*
+==========================
+R_TransformModelToClip
+
+==========================
+*/
+void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
+							vec4_t eye, vec4_t dst ) {
+	int i;
+
+	for ( i = 0 ; i < 4 ; i++ ) {
+		eye[i] = 
+			src[0] * modelMatrix[ i + 0 * 4 ] +
+			src[1] * modelMatrix[ i + 1 * 4 ] +
+			src[2] * modelMatrix[ i + 2 * 4 ] +
+			1 * modelMatrix[ i + 3 * 4 ];
+	}
+
+	for ( i = 0 ; i < 4 ; i++ ) {
+		dst[i] = 
+			eye[0] * projectionMatrix[ i + 0 * 4 ] +
+			eye[1] * projectionMatrix[ i + 1 * 4 ] +
+			eye[2] * projectionMatrix[ i + 2 * 4 ] +
+			eye[3] * projectionMatrix[ i + 3 * 4 ];
+	}
+}
+
+/*
+==========================
+R_TransformClipToWindow
+
+==========================
+*/
+void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) {
+	normalized[0] = clip[0] / clip[3];
+	normalized[1] = clip[1] / clip[3];
+	normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] );
+
+	window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth;
+	window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight;
+	window[2] = normalized[2];
+
+	window[0] = (int) ( window[0] + 0.5 );
+	window[1] = (int) ( window[1] + 0.5 );
+}
+
+
+/*
+==========================
+myGlMultMatrix
+
+==========================
+*/
+void myGlMultMatrix( const float *a, const float *b, float *out ) {
+	int		i, j;
+
+	for ( i = 0 ; i < 4 ; i++ ) {
+		for ( j = 0 ; j < 4 ; j++ ) {
+			out[ i * 4 + j ] =
+				a [ i * 4 + 0 ] * b [ 0 * 4 + j ]
+				+ a [ i * 4 + 1 ] * b [ 1 * 4 + j ]
+				+ a [ i * 4 + 2 ] * b [ 2 * 4 + j ]
+				+ a [ i * 4 + 3 ] * b [ 3 * 4 + j ];
+		}
+	}
+}
+
+/*
+=================
+R_RotateForEntity
+
+Generates an orientation for an entity and viewParms
+Does NOT produce any GL calls
+Called by both the front end and the back end
+=================
+*/
+void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms,
+					   orientationr_t *or ) {
+	float	glMatrix[16];
+	vec3_t	delta;
+	float	axisLength;
+
+	if ( ent->e.reType != RT_MODEL ) {
+		*or = viewParms->world;
+		return;
+	}
+
+	VectorCopy( ent->e.origin, or->origin );
+
+	VectorCopy( ent->e.axis[0], or->axis[0] );
+	VectorCopy( ent->e.axis[1], or->axis[1] );
+	VectorCopy( ent->e.axis[2], or->axis[2] );
+
+	glMatrix[0] = or->axis[0][0];
+	glMatrix[4] = or->axis[1][0];
+	glMatrix[8] = or->axis[2][0];
+	glMatrix[12] = or->origin[0];
+
+	glMatrix[1] = or->axis[0][1];
+	glMatrix[5] = or->axis[1][1];
+	glMatrix[9] = or->axis[2][1];
+	glMatrix[13] = or->origin[1];
+
+	glMatrix[2] = or->axis[0][2];
+	glMatrix[6] = or->axis[1][2];
+	glMatrix[10] = or->axis[2][2];
+	glMatrix[14] = or->origin[2];
+
+	glMatrix[3] = 0;
+	glMatrix[7] = 0;
+	glMatrix[11] = 0;
+	glMatrix[15] = 1;
+
+	Matrix16Copy(glMatrix, or->transformMatrix);
+	myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix );
+
+	// calculate the viewer origin in the model's space
+	// needed for fog, specular, and environment mapping
+	VectorSubtract( viewParms->or.origin, or->origin, delta );
+
+	// compensate for scale in the axes if necessary
+	if ( ent->e.nonNormalizedAxes ) {
+		axisLength = VectorLength( ent->e.axis[0] );
+		if ( !axisLength ) {
+			axisLength = 0;
+		} else {
+			axisLength = 1.0f / axisLength;
+		}
+	} else {
+		axisLength = 1.0f;
+	}
+
+	or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength;
+	or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength;
+	or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength;
+}
+
+/*
+=================
+R_RotateForViewer
+
+Sets up the modelview matrix for a given viewParm
+=================
+*/
+void R_RotateForViewer (void) 
+{
+	float	viewerMatrix[16];
+	vec3_t	origin;
+
+	Com_Memset (&tr.or, 0, sizeof(tr.or));
+	tr.or.axis[0][0] = 1;
+	tr.or.axis[1][1] = 1;
+	tr.or.axis[2][2] = 1;
+	VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin);
+
+	// transform by the camera placement
+	VectorCopy( tr.viewParms.or.origin, origin );
+
+	viewerMatrix[0] = tr.viewParms.or.axis[0][0];
+	viewerMatrix[4] = tr.viewParms.or.axis[0][1];
+	viewerMatrix[8] = tr.viewParms.or.axis[0][2];
+	viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
+
+	viewerMatrix[1] = tr.viewParms.or.axis[1][0];
+	viewerMatrix[5] = tr.viewParms.or.axis[1][1];
+	viewerMatrix[9] = tr.viewParms.or.axis[1][2];
+	viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
+
+	viewerMatrix[2] = tr.viewParms.or.axis[2][0];
+	viewerMatrix[6] = tr.viewParms.or.axis[2][1];
+	viewerMatrix[10] = tr.viewParms.or.axis[2][2];
+	viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10];
+
+	viewerMatrix[3] = 0;
+	viewerMatrix[7] = 0;
+	viewerMatrix[11] = 0;
+	viewerMatrix[15] = 1;
+
+	// convert from our coordinate system (looking down X)
+	// to OpenGL's coordinate system (looking down -Z)
+	myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix );
+
+	tr.viewParms.world = tr.or;
+
+}
+
+/*
+** SetFarClip
+*/
+static void R_SetFarClip( void )
+{
+	float	farthestCornerDistance = 0;
+	int		i;
+
+	// if not rendering the world (icons, menus, etc)
+	// set a 2k far clip plane
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		tr.viewParms.zFar = 2048;
+		return;
+	}
+
+	//
+	// set far clipping planes dynamically
+	//
+	farthestCornerDistance = 0;
+	for ( i = 0; i < 8; i++ )
+	{
+		vec3_t v;
+		vec3_t vecTo;
+		float distance;
+
+		if ( i & 1 )
+		{
+			v[0] = tr.viewParms.visBounds[0][0];
+		}
+		else
+		{
+			v[0] = tr.viewParms.visBounds[1][0];
+		}
+
+		if ( i & 2 )
+		{
+			v[1] = tr.viewParms.visBounds[0][1];
+		}
+		else
+		{
+			v[1] = tr.viewParms.visBounds[1][1];
+		}
+
+		if ( i & 4 )
+		{
+			v[2] = tr.viewParms.visBounds[0][2];
+		}
+		else
+		{
+			v[2] = tr.viewParms.visBounds[1][2];
+		}
+
+		VectorSubtract( v, tr.viewParms.or.origin, vecTo );
+
+		distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2];
+
+		if ( distance > farthestCornerDistance )
+		{
+			farthestCornerDistance = distance;
+		}
+	}
+	tr.viewParms.zFar = sqrt( farthestCornerDistance );
+}
+
+/*
+=================
+R_SetupFrustum
+
+Set up the culling frustum planes for the current view using the results we got from computing the first two rows of
+the projection matrix.
+=================
+*/
+void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float zFar, float stereoSep)
+{
+	vec3_t ofsorigin;
+	float oppleg, adjleg, length;
+	int i;
+	
+	if(stereoSep == 0 && xmin == -xmax)
+	{
+		// symmetric case can be simplified
+		VectorCopy(dest->or.origin, ofsorigin);
+
+		length = sqrt(xmax * xmax + zProj * zProj);
+		oppleg = xmax / length;
+		adjleg = zProj / length;
+
+		VectorScale(dest->or.axis[0], oppleg, dest->frustum[0].normal);
+		VectorMA(dest->frustum[0].normal, adjleg, dest->or.axis[1], dest->frustum[0].normal);
+
+		VectorScale(dest->or.axis[0], oppleg, dest->frustum[1].normal);
+		VectorMA(dest->frustum[1].normal, -adjleg, dest->or.axis[1], dest->frustum[1].normal);
+	}
+	else
+	{
+		// In stereo rendering, due to the modification of the projection matrix, dest->or.origin is not the
+		// actual origin that we're rendering so offset the tip of the view pyramid.
+		VectorMA(dest->or.origin, stereoSep, dest->or.axis[1], ofsorigin);
+	
+		oppleg = xmax + stereoSep;
+		length = sqrt(oppleg * oppleg + zProj * zProj);
+		VectorScale(dest->or.axis[0], oppleg / length, dest->frustum[0].normal);
+		VectorMA(dest->frustum[0].normal, zProj / length, dest->or.axis[1], dest->frustum[0].normal);
+
+		oppleg = xmin + stereoSep;
+		length = sqrt(oppleg * oppleg + zProj * zProj);
+		VectorScale(dest->or.axis[0], -oppleg / length, dest->frustum[1].normal);
+		VectorMA(dest->frustum[1].normal, -zProj / length, dest->or.axis[1], dest->frustum[1].normal);
+	}
+
+	length = sqrt(ymax * ymax + zProj * zProj);
+	oppleg = ymax / length;
+	adjleg = zProj / length;
+
+	VectorScale(dest->or.axis[0], oppleg, dest->frustum[2].normal);
+	VectorMA(dest->frustum[2].normal, adjleg, dest->or.axis[2], dest->frustum[2].normal);
+
+	VectorScale(dest->or.axis[0], oppleg, dest->frustum[3].normal);
+	VectorMA(dest->frustum[3].normal, -adjleg, dest->or.axis[2], dest->frustum[3].normal);
+	
+	for (i=0 ; i<4 ; i++) {
+		dest->frustum[i].type = PLANE_NON_AXIAL;
+		dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal);
+		SetPlaneSignbits( &dest->frustum[i] );
+	}
+
+	if (zFar != 0.0f)
+	{
+		vec3_t farpoint;
+
+		VectorMA(ofsorigin, zFar, dest->or.axis[0], farpoint);
+		VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal);
+
+		dest->frustum[4].type = PLANE_NON_AXIAL;
+		dest->frustum[4].dist = DotProduct (farpoint, dest->frustum[4].normal);
+		SetPlaneSignbits( &dest->frustum[4] );
+	}
+}
+
+/*
+===============
+R_SetupProjection
+===============
+*/
+void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, qboolean computeFrustum)
+{
+	float	xmin, xmax, ymin, ymax;
+	float	width, height, stereoSep = r_stereoSeparation->value;
+
+	/*
+	 * offset the view origin of the viewer for stereo rendering 
+	 * by setting the projection matrix appropriately.
+	 */
+
+	if(stereoSep != 0)
+	{
+		if(dest->stereoFrame == STEREO_LEFT)
+			stereoSep = zProj / stereoSep;
+		else if(dest->stereoFrame == STEREO_RIGHT)
+			stereoSep = zProj / -stereoSep;
+		else
+			stereoSep = 0;
+	}
+
+	ymax = zProj * tan(dest->fovY * M_PI / 360.0f);
+	ymin = -ymax;
+
+	xmax = zProj * tan(dest->fovX * M_PI / 360.0f);
+	xmin = -xmax;
+
+	width = xmax - xmin;
+	height = ymax - ymin;
+	
+	dest->projectionMatrix[0] = 2 * zProj / width;
+	dest->projectionMatrix[4] = 0;
+	dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width;
+	dest->projectionMatrix[12] = 2 * zProj * stereoSep / width;
+
+	dest->projectionMatrix[1] = 0;
+	dest->projectionMatrix[5] = 2 * zProj / height;
+	dest->projectionMatrix[9] = ( ymax + ymin ) / height;	// normally 0
+	dest->projectionMatrix[13] = 0;
+
+	dest->projectionMatrix[3] = 0;
+	dest->projectionMatrix[7] = 0;
+	dest->projectionMatrix[11] = -1;
+	dest->projectionMatrix[15] = 0;
+	
+	// Now that we have all the data for the projection matrix we can also setup the view frustum.
+	if(computeFrustum)
+		R_SetupFrustum(dest, xmin, xmax, ymax, zProj, zFar, stereoSep);
+}
+
+/*
+===============
+R_SetupProjectionZ
+
+Sets the z-component transformation part in the projection matrix
+===============
+*/
+void R_SetupProjectionZ(viewParms_t *dest)
+{
+	float zNear, zFar, depth;
+	
+	zNear = r_znear->value;
+	zFar	= dest->zFar;
+
+	depth	= zFar - zNear;
+
+	dest->projectionMatrix[2] = 0;
+	dest->projectionMatrix[6] = 0;
+	dest->projectionMatrix[10] = -( zFar + zNear ) / depth;
+	dest->projectionMatrix[14] = -2 * zFar * zNear / depth;
+
+	if (dest->isPortal)
+	{
+		float	plane[4];
+		float	plane2[4];
+		vec4_t q, c;
+
+		// transform portal plane into camera space
+		plane[0] = dest->portalPlane.normal[0];
+		plane[1] = dest->portalPlane.normal[1];
+		plane[2] = dest->portalPlane.normal[2];
+		plane[3] = dest->portalPlane.dist;
+
+		plane2[0] = -DotProduct (dest->or.axis[1], plane);
+		plane2[1] = DotProduct (dest->or.axis[2], plane);
+		plane2[2] = -DotProduct (dest->or.axis[0], plane);
+		plane2[3] = DotProduct (plane, dest->or.origin) - plane[3];
+
+		// Lengyel, Eric. “Modifying the Projection Matrix to Perform Oblique Near-plane Clipping”.
+		// Terathon Software 3D Graphics Library, 2004. http://www.terathon.com/code/oblique.html
+		q[0] = (SGN(plane2[0]) + dest->projectionMatrix[8]) / dest->projectionMatrix[0];
+		q[1] = (SGN(plane2[1]) + dest->projectionMatrix[9]) / dest->projectionMatrix[5];
+		q[2] = -1.0f;
+		q[3] = (1.0f + dest->projectionMatrix[10]) / dest->projectionMatrix[14];
+
+		VectorScale4(plane2, 2.0f / DotProduct4(plane2, q), c);
+
+		dest->projectionMatrix[2]  = c[0];
+		dest->projectionMatrix[6]  = c[1];
+		dest->projectionMatrix[10] = c[2] + 1.0f;
+		dest->projectionMatrix[14] = c[3];
+
+	}
+
+}
+
+/*
+===============
+R_SetupProjectionOrtho
+===============
+*/
+void R_SetupProjectionOrtho(viewParms_t *dest, vec3_t viewBounds[2])
+{
+	float xmin, xmax, ymin, ymax, znear, zfar;
+	//viewParms_t *dest = &tr.viewParms;
+	int i;
+	vec3_t pop;
+
+	// Quake3:   Projection:
+	//
+	//    Z  X   Y  Z
+	//    | /    | /
+	//    |/     |/
+	// Y--+      +--X
+
+	xmin  =  viewBounds[0][1];
+	xmax  =  viewBounds[1][1];
+	ymin  = -viewBounds[1][2];
+	ymax  = -viewBounds[0][2];
+	znear =  viewBounds[0][0];
+	zfar  =  viewBounds[1][0];
+
+	dest->projectionMatrix[0]  = 2 / (xmax - xmin);
+	dest->projectionMatrix[4]  = 0;
+	dest->projectionMatrix[8]  = 0;
+	dest->projectionMatrix[12] = (xmax + xmin) / (xmax - xmin);
+
+	dest->projectionMatrix[1]  = 0;
+	dest->projectionMatrix[5]  = 2 / (ymax - ymin);
+	dest->projectionMatrix[9]  = 0;
+	dest->projectionMatrix[13] = (ymax + ymin) / (ymax - ymin);
+
+	dest->projectionMatrix[2]  = 0;
+	dest->projectionMatrix[6]  = 0;
+	dest->projectionMatrix[10] = -2 / (zfar - znear);
+	dest->projectionMatrix[14] = -(zfar + znear) / (zfar - znear);
+
+	dest->projectionMatrix[3]  = 0;
+	dest->projectionMatrix[7]  = 0;
+	dest->projectionMatrix[11] = 0;
+	dest->projectionMatrix[15] = 1;
+
+	VectorScale(dest->or.axis[1],  1.0f, dest->frustum[0].normal);
+	VectorMA(dest->or.origin, viewBounds[0][1], dest->frustum[0].normal, pop);
+	dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal);
+
+	VectorScale(dest->or.axis[1], -1.0f, dest->frustum[1].normal);
+	VectorMA(dest->or.origin, -viewBounds[1][1], dest->frustum[1].normal, pop);
+	dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal);
+
+	VectorScale(dest->or.axis[2],  1.0f, dest->frustum[2].normal);
+	VectorMA(dest->or.origin, viewBounds[0][2], dest->frustum[2].normal, pop);
+	dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal);
+
+	VectorScale(dest->or.axis[2], -1.0f, dest->frustum[3].normal);
+	VectorMA(dest->or.origin, -viewBounds[1][2], dest->frustum[3].normal, pop);
+	dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal);
+
+	VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal);
+	VectorMA(dest->or.origin, -viewBounds[1][0], dest->frustum[4].normal, pop);
+	dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal);
+	
+	for (i = 0; i < 5; i++)
+	{
+		dest->frustum[i].type = PLANE_NON_AXIAL;
+		SetPlaneSignbits (&dest->frustum[i]);
+	}
+}
+
+/*
+=================
+R_MirrorPoint
+=================
+*/
+void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
+	int		i;
+	vec3_t	local;
+	vec3_t	transformed;
+	float	d;
+
+	VectorSubtract( in, surface->origin, local );
+
+	VectorClear( transformed );
+	for ( i = 0 ; i < 3 ; i++ ) {
+		d = DotProduct(local, surface->axis[i]);
+		VectorMA( transformed, d, camera->axis[i], transformed );
+	}
+
+	VectorAdd( transformed, camera->origin, out );
+}
+
+void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
+	int		i;
+	float	d;
+
+	VectorClear( out );
+	for ( i = 0 ; i < 3 ; i++ ) {
+		d = DotProduct(in, surface->axis[i]);
+		VectorMA( out, d, camera->axis[i], out );
+	}
+}
+
+
+/*
+=============
+R_PlaneForSurface
+=============
+*/
+void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) {
+	srfTriangles_t	*tri;
+	srfPoly_t		*poly;
+	srfVert_t		*v1, *v2, *v3;
+	vec4_t			plane4;
+
+	if (!surfType) {
+		Com_Memset (plane, 0, sizeof(*plane));
+		plane->normal[0] = 1;
+		return;
+	}
+	switch (*surfType) {
+	case SF_FACE:
+		*plane = ((srfSurfaceFace_t *)surfType)->plane;
+		return;
+	case SF_TRIANGLES:
+		tri = (srfTriangles_t *)surfType;
+		v1 = tri->verts + tri->triangles[0].indexes[0];
+		v2 = tri->verts + tri->triangles[0].indexes[1];
+		v3 = tri->verts + tri->triangles[0].indexes[2];
+		PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
+		VectorCopy( plane4, plane->normal ); 
+		plane->dist = plane4[3];
+		return;
+	case SF_POLY:
+		poly = (srfPoly_t *)surfType;
+		PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz );
+		VectorCopy( plane4, plane->normal ); 
+		plane->dist = plane4[3];
+		return;
+	default:
+		Com_Memset (plane, 0, sizeof(*plane));
+		plane->normal[0] = 1;		
+		return;
+	}
+}
+
+/*
+=================
+R_GetPortalOrientation
+
+entityNum is the entity that the portal surface is a part of, which may
+be moving and rotating.
+
+Returns qtrue if it should be mirrored
+=================
+*/
+qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, 
+							 orientation_t *surface, orientation_t *camera,
+							 vec3_t pvsOrigin, qboolean *mirror ) {
+	int			i;
+	cplane_t	originalPlane, plane;
+	trRefEntity_t	*e;
+	float		d;
+	vec3_t		transformed;
+
+	// create plane axis for the portal we are seeing
+	R_PlaneForSurface( drawSurf->surface, &originalPlane );
+
+	// rotate the plane if necessary
+	if ( entityNum != REFENTITYNUM_WORLD ) {
+		tr.currentEntityNum = entityNum;
+		tr.currentEntity = &tr.refdef.entities[entityNum];
+
+		// get the orientation of the entity
+		R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
+
+		// rotate the plane, but keep the non-rotated version for matching
+		// against the portalSurface entities
+		R_LocalNormalToWorld( originalPlane.normal, plane.normal );
+		plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
+
+		// translate the original plane
+		originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
+	} else {
+		plane = originalPlane;
+	}
+
+	VectorCopy( plane.normal, surface->axis[0] );
+	PerpendicularVector( surface->axis[1], surface->axis[0] );
+	CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] );
+
+	// locate the portal entity closest to this plane.
+	// origin will be the origin of the portal, origin2 will be
+	// the origin of the camera
+	for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) {
+		e = &tr.refdef.entities[i];
+		if ( e->e.reType != RT_PORTALSURFACE ) {
+			continue;
+		}
+
+		d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
+		if ( d > 64 || d < -64) {
+			continue;
+		}
+
+		// get the pvsOrigin from the entity
+		VectorCopy( e->e.oldorigin, pvsOrigin );
+
+		// if the entity is just a mirror, don't use as a camera point
+		if ( e->e.oldorigin[0] == e->e.origin[0] && 
+			e->e.oldorigin[1] == e->e.origin[1] && 
+			e->e.oldorigin[2] == e->e.origin[2] ) {
+			VectorScale( plane.normal, plane.dist, surface->origin );
+			VectorCopy( surface->origin, camera->origin );
+			VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] );
+			VectorCopy( surface->axis[1], camera->axis[1] );
+			VectorCopy( surface->axis[2], camera->axis[2] );
+
+			*mirror = qtrue;
+			return qtrue;
+		}
+
+		// project the origin onto the surface plane to get
+		// an origin point we can rotate around
+		d = DotProduct( e->e.origin, plane.normal ) - plane.dist;
+		VectorMA( e->e.origin, -d, surface->axis[0], surface->origin );
+			
+		// now get the camera origin and orientation
+		VectorCopy( e->e.oldorigin, camera->origin );
+		AxisCopy( e->e.axis, camera->axis );
+		VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] );
+		VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] );
+
+		// optionally rotate
+		if ( e->e.oldframe ) {
+			// if a speed is specified
+			if ( e->e.frame ) {
+				// continuous rotate
+				d = (tr.refdef.time/1000.0f) * e->e.frame;
+				VectorCopy( camera->axis[1], transformed );
+				RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+				CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+			} else {
+				// bobbing rotate, with skinNum being the rotation offset
+				d = sin( tr.refdef.time * 0.003f );
+				d = e->e.skinNum + d * 4;
+				VectorCopy( camera->axis[1], transformed );
+				RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+				CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+			}
+		}
+		else if ( e->e.skinNum ) {
+			d = e->e.skinNum;
+			VectorCopy( camera->axis[1], transformed );
+			RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+			CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+		}
+		*mirror = qfalse;
+		return qtrue;
+	}
+
+	// if we didn't locate a portal entity, don't render anything.
+	// We don't want to just treat it as a mirror, because without a
+	// portal entity the server won't have communicated a proper entity set
+	// in the snapshot
+
+	// unfortunately, with local movement prediction it is easily possible
+	// to see a surface before the server has communicated the matching
+	// portal surface entity, so we don't want to print anything here...
+
+	//ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" );
+
+	return qfalse;
+}
+
+static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum )
+{
+	int			i;
+	cplane_t	originalPlane, plane;
+	trRefEntity_t	*e;
+	float		d;
+
+	// create plane axis for the portal we are seeing
+	R_PlaneForSurface( drawSurf->surface, &originalPlane );
+
+	// rotate the plane if necessary
+	if ( entityNum != REFENTITYNUM_WORLD ) 
+	{
+		tr.currentEntityNum = entityNum;
+		tr.currentEntity = &tr.refdef.entities[entityNum];
+
+		// get the orientation of the entity
+		R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
+
+		// rotate the plane, but keep the non-rotated version for matching
+		// against the portalSurface entities
+		R_LocalNormalToWorld( originalPlane.normal, plane.normal );
+		plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
+
+		// translate the original plane
+		originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
+	} 
+	else 
+	{
+		plane = originalPlane;
+	}
+
+	// locate the portal entity closest to this plane.
+	// origin will be the origin of the portal, origin2 will be
+	// the origin of the camera
+	for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) 
+	{
+		e = &tr.refdef.entities[i];
+		if ( e->e.reType != RT_PORTALSURFACE ) {
+			continue;
+		}
+
+		d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
+		if ( d > 64 || d < -64) {
+			continue;
+		}
+
+		// if the entity is just a mirror, don't use as a camera point
+		if ( e->e.oldorigin[0] == e->e.origin[0] && 
+			e->e.oldorigin[1] == e->e.origin[1] && 
+			e->e.oldorigin[2] == e->e.origin[2] ) 
+		{
+			return qtrue;
+		}
+
+		return qfalse;
+	}
+	return qfalse;
+}
+
+/*
+** SurfIsOffscreen
+**
+** Determines if a surface is completely offscreen.
+*/
+static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) {
+	float shortest = 100000000;
+	int entityNum;
+	int numTriangles;
+	shader_t *shader;
+	int		fogNum;
+	int dlighted;
+	int pshadowed;
+	vec4_t clip, eye;
+	int i;
+	unsigned int pointOr = 0;
+	unsigned int pointAnd = (unsigned int)~0;
+
+	if ( glConfig.smpActive ) {		// FIXME!  we can't do RB_BeginSurface/RB_EndSurface stuff with smp!
+		return qfalse;
+	}
+
+	R_RotateForViewer();
+
+	R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed );
+	RB_BeginSurface( shader, fogNum );
+	rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+
+	assert( tess.numVertexes < 128 );
+
+	for ( i = 0; i < tess.numVertexes; i++ )
+	{
+		int j;
+		unsigned int pointFlags = 0;
+
+		R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip );
+
+		for ( j = 0; j < 3; j++ )
+		{
+			if ( clip[j] >= clip[3] )
+			{
+				pointFlags |= (1 << (j*2));
+			}
+			else if ( clip[j] <= -clip[3] )
+			{
+				pointFlags |= ( 1 << (j*2+1));
+			}
+		}
+		pointAnd &= pointFlags;
+		pointOr |= pointFlags;
+	}
+
+	// trivially reject
+	if ( pointAnd )
+	{
+		return qtrue;
+	}
+
+	// determine if this surface is backfaced and also determine the distance
+	// to the nearest vertex so we can cull based on portal range.  Culling
+	// based on vertex distance isn't 100% correct (we should be checking for
+	// range to the surface), but it's good enough for the types of portals
+	// we have in the game right now.
+	numTriangles = tess.numIndexes / 3;
+
+	for ( i = 0; i < tess.numIndexes; i += 3 )
+	{
+		vec3_t normal;
+		float len;
+
+		VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal );
+
+		len = VectorLengthSquared( normal );			// lose the sqrt
+		if ( len < shortest )
+		{
+			shortest = len;
+		}
+
+		if ( DotProduct( normal, tess.normal[tess.indexes[i]] ) >= 0 )
+		{
+			numTriangles--;
+		}
+	}
+	if ( !numTriangles )
+	{
+		return qtrue;
+	}
+
+	// mirrors can early out at this point, since we don't do a fade over distance
+	// with them (although we could)
+	if ( IsMirror( drawSurf, entityNum ) )
+	{
+		return qfalse;
+	}
+
+	if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) )
+	{
+		return qtrue;
+	}
+
+	return qfalse;
+}
+
+/*
+========================
+R_MirrorViewBySurface
+
+Returns qtrue if another view has been rendered
+========================
+*/
+qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) {
+	vec4_t			clipDest[128];
+	viewParms_t		newParms;
+	viewParms_t		oldParms;
+	orientation_t	surface, camera;
+
+	// don't recursively mirror
+	if (tr.viewParms.isPortal) {
+		ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" );
+		return qfalse;
+	}
+
+	if ( r_noportals->integer || (r_fastsky->integer == 1) ) {
+		return qfalse;
+	}
+
+	// trivially reject portal/mirror
+	if ( SurfIsOffscreen( drawSurf, clipDest ) ) {
+		return qfalse;
+	}
+
+	// save old viewParms so we can return to it after the mirror view
+	oldParms = tr.viewParms;
+
+	newParms = tr.viewParms;
+	newParms.isPortal = qtrue;
+	if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, 
+		newParms.pvsOrigin, &newParms.isMirror ) ) {
+		return qfalse;		// bad portal, no portalentity
+	}
+
+	R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin );
+
+	VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
+	newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
+	
+	R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]);
+	R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]);
+	R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]);
+
+	// OPTIMIZE: restrict the viewport on the mirrored view
+
+	// render the mirror view
+	R_RenderView (&newParms);
+
+	tr.viewParms = oldParms;
+
+	return qtrue;
+}
+
+/*
+=================
+R_SpriteFogNum
+
+See if a sprite is inside a fog volume
+=================
+*/
+int R_SpriteFogNum( trRefEntity_t *ent ) {
+	int				i, j;
+	fog_t			*fog;
+
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		return 0;
+	}
+
+	for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+		fog = &tr.world->fogs[i];
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) {
+				break;
+			}
+			if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) {
+				break;
+			}
+		}
+		if ( j == 3 ) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+/*
+==========================================================================================
+
+DRAWSURF SORTING
+
+==========================================================================================
+*/
+
+/*
+===============
+R_Radix
+===============
+*/
+static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest )
+{
+  int           count[ 256 ] = { 0 };
+  int           index[ 256 ];
+  int           i;
+  unsigned char *sortKey = NULL;
+  unsigned char *end = NULL;
+
+  sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
+  end = sortKey + ( size * sizeof( drawSurf_t ) );
+  for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) )
+    ++count[ *sortKey ];
+
+  index[ 0 ] = 0;
+
+  for( i = 1; i < 256; ++i )
+    index[ i ] = index[ i - 1 ] + count[ i - 1 ];
+
+  sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
+  for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) )
+    dest[ index[ *sortKey ]++ ] = source[ i ];
+}
+
+/*
+===============
+R_RadixSort
+
+Radix sort with 4 byte size buckets
+===============
+*/
+static void R_RadixSort( drawSurf_t *source, int size )
+{
+  static drawSurf_t scratch[ MAX_DRAWSURFS ];
+#ifdef Q3_LITTLE_ENDIAN
+  R_Radix( 0, size, source, scratch );
+  R_Radix( 1, size, scratch, source );
+  R_Radix( 2, size, source, scratch );
+  R_Radix( 3, size, scratch, source );
+#else
+  R_Radix( 3, size, source, scratch );
+  R_Radix( 2, size, scratch, source );
+  R_Radix( 1, size, source, scratch );
+  R_Radix( 0, size, scratch, source );
+#endif //Q3_LITTLE_ENDIAN
+}
+
+//==========================================================================================
+
+/*
+=================
+R_AddDrawSurf
+=================
+*/
+void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, 
+				   int fogIndex, int dlightMap, int pshadowMap ) {
+	int			index;
+
+	// instead of checking for overflow, we just mask the index
+	// so it wraps around
+	index = tr.refdef.numDrawSurfs & DRAWSURF_MASK;
+	// the sort data is packed into a single 32 bit value so it can be
+	// compared quickly during the qsorting process
+	tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) 
+		| tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) 
+		| ((int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap;
+	tr.refdef.drawSurfs[index].surface = surface;
+	tr.refdef.numDrawSurfs++;
+}
+
+/*
+=================
+R_DecomposeSort
+=================
+*/
+void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, 
+					 int *fogNum, int *dlightMap, int *pshadowMap ) {
+	*fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31;
+	*shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
+	*entityNum = ( sort >> QSORT_REFENTITYNUM_SHIFT ) & REFENTITYNUM_MASK;
+	*pshadowMap = (sort & 2) >> 1;
+	*dlightMap = sort & 1;
+}
+
+/*
+=================
+R_SortDrawSurfs
+=================
+*/
+void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+	shader_t		*shader;
+	int				fogNum;
+	int				entityNum;
+	int				dlighted;
+	int             pshadowed;
+	int				i;
+
+	//ri.Printf(PRINT_ALL, "firstDrawSurf %d numDrawSurfs %d\n", (int)(drawSurfs - tr.refdef.drawSurfs), numDrawSurfs);
+
+	// it is possible for some views to not have any surfaces
+	if ( numDrawSurfs < 1 ) {
+		// we still need to add it for hyperspace cases
+		R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
+		return;
+	}
+
+	// if we overflowed MAX_DRAWSURFS, the drawsurfs
+	// wrapped around in the buffer and we will be missing
+	// the first surfaces, not the last ones
+	if ( numDrawSurfs > MAX_DRAWSURFS ) {
+		numDrawSurfs = MAX_DRAWSURFS;
+	}
+
+	// sort the drawsurfs by sort type, then orientation, then shader
+	R_RadixSort( drawSurfs, numDrawSurfs );
+
+	// skip pass through drawing if rendering a shadow map
+	if (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))
+	{
+		R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
+		return;
+	}
+
+	// check for any pass through drawing, which
+	// may cause another view to be rendered first
+	for ( i = 0 ; i < numDrawSurfs ; i++ ) {
+		R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed );
+
+		if ( shader->sort > SS_PORTAL ) {
+			break;
+		}
+
+		// no shader should ever have this sort type
+		if ( shader->sort == SS_BAD ) {
+			ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name );
+		}
+
+		// if the mirror was completely clipped away, we may need to check another surface
+		if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) {
+			// this is a debug option to see exactly what is being mirrored
+			if ( r_portalOnly->integer ) {
+				return;
+			}
+			break;		// only one mirror view at a time
+		}
+	}
+
+	R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
+}
+
+static void R_AddEntitySurface (int entityNum)
+{
+	trRefEntity_t	*ent;
+	shader_t		*shader;
+
+	tr.currentEntityNum = entityNum;
+
+	ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum];
+
+	ent->needDlights = qfalse;
+
+	// preshift the value we are going to OR into the drawsurf sort
+	tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT;
+
+	//
+	// the weapon model must be handled special --
+	// we don't want the hacked weapon position showing in 
+	// mirrors, because the true body position will already be drawn
+	//
+	if ( (ent->e.renderfx & RF_FIRST_PERSON) && (tr.viewParms.isPortal 
+	      || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))) ) {
+		return;
+	}
+
+	// simple generated models, like sprites and beams, are not culled
+	switch ( ent->e.reType ) {
+	case RT_PORTALSURFACE:
+		break;		// don't draw anything
+	case RT_SPRITE:
+	case RT_BEAM:
+	case RT_LIGHTNING:
+	case RT_RAIL_CORE:
+	case RT_RAIL_RINGS:
+		// self blood sprites, talk balloons, etc should not be drawn in the primary
+		// view.  We can't just do this check for all entities, because md3
+		// entities may still want to cast shadows from them
+		if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
+			return;
+		}
+		shader = R_GetShaderByHandle( ent->e.customShader );
+		R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0, 0 );
+		break;
+
+	case RT_MODEL:
+		// we must set up parts of tr.or for model culling
+		R_RotateForEntity( ent, &tr.viewParms, &tr.or );
+
+		tr.currentModel = R_GetModelByHandle( ent->e.hModel );
+		if (!tr.currentModel) {
+			R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0 );
+		} else {
+			switch ( tr.currentModel->type ) {
+			case MOD_MESH:
+				R_AddMD3Surfaces( ent );
+				break;
+			case MOD_MD4:
+				R_AddAnimSurfaces( ent );
+				break;
+#ifdef RAVENMD4
+			case MOD_MDR:
+				R_MDRAddAnimSurfaces( ent );
+				break;
+#endif
+			case MOD_IQM:
+				R_AddIQMSurfaces( ent );
+				break;
+			case MOD_BRUSH:
+				R_AddBrushModelSurfaces( ent );
+				break;
+			case MOD_BAD:		// null model axis
+				if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
+					break;
+				}
+				R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0 );
+				break;
+			default:
+				ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" );
+				break;
+			}
+		}
+		break;
+	default:
+		ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" );
+	}
+}
+
+/*
+=============
+R_AddEntitySurfaces
+=============
+*/
+void R_AddEntitySurfaces (void) {
+	int i;
+
+	if ( !r_drawentities->integer ) {
+		return;
+	}
+
+	for ( i = 0; i < tr.refdef.num_entities; i++)
+		R_AddEntitySurface(i);
+}
+
+
+/*
+====================
+R_GenerateDrawSurfs
+====================
+*/
+void R_GenerateDrawSurfs( void ) {
+	R_AddWorldSurfaces ();
+
+	R_AddPolygonSurfaces();
+
+	// set the projection matrix with the minimum zfar
+	// now that we have the world bounded
+	// this needs to be done before entities are
+	// added, because they use the projection
+	// matrix for lod calculation
+
+	// dynamically compute far clip plane distance
+	if (!(tr.viewParms.flags & VPF_SHADOWMAP))
+	{
+		R_SetFarClip();
+	}
+
+	// we know the size of the clipping volume. Now set the rest of the projection matrix.
+	R_SetupProjectionZ (&tr.viewParms);
+
+	R_AddEntitySurfaces ();
+}
+
+/*
+================
+R_DebugPolygon
+================
+*/
+void R_DebugPolygon( int color, int numPoints, float *points ) {
+	// FIXME: implement this
+#if 0
+	int		i;
+
+	GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+	// draw solid shade
+
+	qglColor3f( color&1, (color>>1)&1, (color>>2)&1 );
+	qglBegin( GL_POLYGON );
+	for ( i = 0 ; i < numPoints ; i++ ) {
+		qglVertex3fv( points + i * 3 );
+	}
+	qglEnd();
+
+	// draw wireframe outline
+	GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+	qglDepthRange( 0, 0 );
+	qglColor3f( 1, 1, 1 );
+	qglBegin( GL_POLYGON );
+	for ( i = 0 ; i < numPoints ; i++ ) {
+		qglVertex3fv( points + i * 3 );
+	}
+	qglEnd();
+	qglDepthRange( 0, 1 );
+#endif
+}
+
+/*
+====================
+R_DebugGraphics
+
+Visualization aid for movement clipping debugging
+====================
+*/
+void R_DebugGraphics( void ) {
+	if ( !r_debugSurface->integer ) {
+		return;
+	}
+
+	// the render thread can't make callbacks to the main thread
+	R_SyncRenderThread();
+
+	GL_Bind( tr.whiteImage);
+	GL_Cull( CT_FRONT_SIDED );
+	ri.CM_DrawDebugSurface( R_DebugPolygon );
+}
+
+
+/*
+================
+R_RenderView
+
+A view may be either the actual camera view,
+or a mirror / remote location
+================
+*/
+void R_RenderView (viewParms_t *parms) {
+	int		firstDrawSurf;
+
+	if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) {
+		return;
+	}
+
+	tr.viewCount++;
+
+	tr.viewParms = *parms;
+	tr.viewParms.frameSceneNum = tr.frameSceneNum;
+	tr.viewParms.frameCount = tr.frameCount;
+
+	firstDrawSurf = tr.refdef.numDrawSurfs;
+
+	tr.viewCount++;
+
+	// set viewParms.world
+	R_RotateForViewer ();
+
+	R_SetupProjection(&tr.viewParms, r_zproj->value, tr.viewParms.zFar, qtrue);
+
+	R_GenerateDrawSurfs();
+
+	R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf );
+
+	// draw main system development information (surface outlines, etc)
+	R_DebugGraphics();
+}
+
+
+void R_RenderDlightCubemaps(const refdef_t *fd)
+{
+	int i;
+
+	for (i = 0; i < tr.refdef.num_dlights; i++)
+	{
+		viewParms_t		shadowParms;
+		int j;
+
+		// use previous frame to determine visible dlights
+		if ((1 << i) & tr.refdef.dlightMask)
+			continue;
+
+		Com_Memset( &shadowParms, 0, sizeof( shadowParms ) );
+
+		shadowParms.viewportX = tr.refdef.x;
+		shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE );
+		shadowParms.viewportWidth = PSHADOW_MAP_SIZE;
+		shadowParms.viewportHeight = PSHADOW_MAP_SIZE;
+		shadowParms.isPortal = qfalse;
+		shadowParms.isMirror = qtrue; // because it is
+
+		shadowParms.fovX = 90;
+		shadowParms.fovY = 90;
+
+		shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW;
+		shadowParms.zFar = tr.refdef.dlights[i].radius;
+
+		VectorCopy( tr.refdef.dlights[i].origin, shadowParms.or.origin );
+
+		for (j = 0; j < 6; j++)
+		{
+			switch(j)
+			{
+				case 0:
+					// -X
+					VectorSet( shadowParms.or.axis[0], -1,  0,  0);
+					VectorSet( shadowParms.or.axis[1],  0,  0, -1);
+					VectorSet( shadowParms.or.axis[2],  0,  1,  0);
+					break;
+				case 1: 
+					// +X
+					VectorSet( shadowParms.or.axis[0],  1,  0,  0);
+					VectorSet( shadowParms.or.axis[1],  0,  0,  1);
+					VectorSet( shadowParms.or.axis[2],  0,  1,  0);
+					break;
+				case 2: 
+					// -Y
+					VectorSet( shadowParms.or.axis[0],  0, -1,  0);
+					VectorSet( shadowParms.or.axis[1],  1,  0,  0);
+					VectorSet( shadowParms.or.axis[2],  0,  0, -1);
+					break;
+				case 3: 
+					// +Y
+					VectorSet( shadowParms.or.axis[0],  0,  1,  0);
+					VectorSet( shadowParms.or.axis[1],  1,  0,  0);
+					VectorSet( shadowParms.or.axis[2],  0,  0,  1);
+					break;
+				case 4:
+					// -Z
+					VectorSet( shadowParms.or.axis[0],  0,  0, -1);
+					VectorSet( shadowParms.or.axis[1],  1,  0,  0);
+					VectorSet( shadowParms.or.axis[2],  0,  1,  0);
+					break;
+				case 5:
+					// +Z
+					VectorSet( shadowParms.or.axis[0],  0,  0,  1);
+					VectorSet( shadowParms.or.axis[1], -1,  0,  0);
+					VectorSet( shadowParms.or.axis[2],  0,  1,  0);
+					break;
+			}
+
+			R_RenderView(&shadowParms);
+			R_AddCapShadowmapCmd( i, j );
+		}
+	}
+}
+
+
+void R_RenderPshadowMaps(const refdef_t *fd)
+{
+	viewParms_t		shadowParms;
+	int i;
+
+	// first, make a list of shadows
+	for ( i = 0; i < tr.refdef.num_entities; i++)
+	{
+		trRefEntity_t *ent = &tr.refdef.entities[i];
+
+		if((ent->e.renderfx & (RF_FIRST_PERSON | RF_NOSHADOW)))
+			continue;
+
+		//if((ent->e.renderfx & RF_THIRD_PERSON))
+			//continue;
+
+		if (ent->e.reType == RT_MODEL)
+		{
+			model_t *model = R_GetModelByHandle( ent->e.hModel );
+			pshadow_t shadow;
+			float radius = 0.0f;
+			float scale = 1.0f;
+			vec3_t diff;
+			int j;
+
+			if (!model)
+				continue;
+
+			if (ent->e.nonNormalizedAxes)
+			{
+				scale = VectorLength( ent->e.axis[0] );
+			}
+
+			switch (model->type)
+			{
+				case MOD_MESH:
+				{
+					mdvFrame_t *frame = &model->mdv[0]->frames[ent->e.frame];
+
+					radius = frame->radius * scale;
+				}
+				break;
+
+				case MOD_MD4:
+				{
+					// FIXME: actually calculate the radius and bounds, this is a horrible hack
+					radius = r_pshadowDist->value / 2.0f;
+				}
+				break;
+#ifdef RAVENMD4
+				case MOD_MDR:
+				{
+					// FIXME: never actually tested this
+					mdrHeader_t *header = model->modelData;
+					int frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+					mdrFrame_t *frame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame);
+
+					radius = frame->radius;
+				}
+				break;
+#endif
+				case MOD_IQM:
+				{
+					// FIXME: never actually tested this
+					iqmData_t *data = model->modelData;
+					vec3_t diag;
+					float *framebounds;
+
+					framebounds = data->bounds + 6*ent->e.frame;
+					VectorSubtract( framebounds+3, framebounds, diag );
+					radius = 0.5f * VectorLength( diag );
+				}
+				break;
+
+				default:
+					break;
+			}
+
+			if (!radius)
+				continue;
+
+			// Cull entities that are behind the viewer by more than lightRadius
+			VectorSubtract(ent->e.origin, fd->vieworg, diff);
+			if (DotProduct(diff, fd->viewaxis[0]) < -r_pshadowDist->value)
+				continue;
+
+			memset(&shadow, 0, sizeof(shadow));
+
+			shadow.numEntities = 1;
+			shadow.entityNums[0] = i;
+			shadow.viewRadius = radius;
+			shadow.lightRadius = r_pshadowDist->value;
+			VectorCopy(ent->e.origin, shadow.viewOrigin);
+			shadow.sort = DotProduct(diff, diff) / (radius * radius);
+			VectorCopy(ent->e.origin, shadow.entityOrigins[0]);
+			shadow.entityRadiuses[0] = radius;
+
+			for (j = 0; j < MAX_CALC_PSHADOWS; j++)
+			{
+				pshadow_t swap;
+
+				if (j + 1 > tr.refdef.num_pshadows)
+				{
+					tr.refdef.num_pshadows = j + 1;
+					tr.refdef.pshadows[j] = shadow;
+					break;
+				}
+
+				// sort shadows by distance from camera divided by radius
+				// FIXME: sort better
+				if (tr.refdef.pshadows[j].sort <= shadow.sort)
+					continue;
+
+				swap = tr.refdef.pshadows[j];
+				tr.refdef.pshadows[j] = shadow;
+				shadow = swap;
+			}
+		}
+	}
+
+	// next, merge touching pshadows
+	for ( i = 0; i < tr.refdef.num_pshadows; i++)
+	{
+		pshadow_t *ps1 = &tr.refdef.pshadows[i];
+		int j;
+
+		for (j = i + 1; j < tr.refdef.num_pshadows; j++)
+		{
+			pshadow_t *ps2 = &tr.refdef.pshadows[j];
+			int k;
+			qboolean touch;
+
+			if (ps1->numEntities == 8)
+				break;
+
+			touch = qfalse;
+			if (SpheresIntersect(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius))
+			{
+				for (k = 0; k < ps1->numEntities; k++)
+				{
+					if (SpheresIntersect(ps1->entityOrigins[k], ps1->entityRadiuses[k], ps2->viewOrigin, ps2->viewRadius))
+					{
+						touch = qtrue;
+						break;
+					}
+				}
+			}
+
+			if (touch)
+			{
+				vec3_t newOrigin;
+				float newRadius;
+
+				BoundingSphereOfSpheres(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius, newOrigin, &newRadius);
+				VectorCopy(newOrigin, ps1->viewOrigin);
+				ps1->viewRadius = newRadius;
+
+				ps1->entityNums[ps1->numEntities] = ps2->entityNums[0];
+				VectorCopy(ps2->viewOrigin, ps1->entityOrigins[ps1->numEntities]);
+				ps1->entityRadiuses[ps1->numEntities] = ps2->viewRadius;
+
+				ps1->numEntities++;
+
+				for (k = j; k < tr.refdef.num_pshadows - 1; k++)
+				{
+					tr.refdef.pshadows[k] = tr.refdef.pshadows[k + 1];
+				}
+
+				j--;
+				tr.refdef.num_pshadows--;
+			}
+		}
+	}
+
+	// cap number of drawn pshadows
+	if (tr.refdef.num_pshadows > MAX_DRAWN_PSHADOWS)
+	{
+		tr.refdef.num_pshadows = MAX_DRAWN_PSHADOWS;
+	}
+
+	// next, fill up the rest of the shadow info
+	for ( i = 0; i < tr.refdef.num_pshadows; i++)
+	{
+		pshadow_t *shadow = &tr.refdef.pshadows[i];
+		vec3_t up;
+		vec3_t ambientLight, directedLight, lightDir;
+
+		VectorSet(lightDir, 0.57735f, 0.57735f, 0.57735f);
+#if 1
+		R_LightForPoint(shadow->viewOrigin, ambientLight, directedLight, lightDir);
+
+		// sometimes there's no light
+		if (DotProduct(lightDir, lightDir) < 0.9f)
+			VectorSet(lightDir, 0.0f, 0.0f, 1.0f);
+#endif
+
+		if (shadow->viewRadius * 3.0f > shadow->lightRadius)
+		{
+			shadow->lightRadius = shadow->viewRadius * 3.0f;
+		}
+
+		VectorMA(shadow->viewOrigin, shadow->viewRadius, lightDir, shadow->lightOrigin);
+
+		// make up a projection, up doesn't matter
+		VectorScale(lightDir, -1.0f, shadow->lightViewAxis[0]);
+		VectorSet(up, 0, 0, -1);
+
+		if ( abs(DotProduct(up, shadow->lightViewAxis[0])) > 0.9f )
+		{
+			VectorSet(up, -1, 0, 0);
+		}
+
+		CrossProduct(shadow->lightViewAxis[0], up, shadow->lightViewAxis[1]);
+		VectorNormalize(shadow->lightViewAxis[1]);
+		CrossProduct(shadow->lightViewAxis[0], shadow->lightViewAxis[1], shadow->lightViewAxis[2]);
+
+		VectorCopy(shadow->lightViewAxis[0], shadow->cullPlane.normal);
+		shadow->cullPlane.dist = DotProduct(shadow->cullPlane.normal, shadow->lightOrigin);
+		shadow->cullPlane.type = PLANE_NON_AXIAL;
+		SetPlaneSignbits(&shadow->cullPlane);
+	}
+
+	// next, render shadowmaps
+	for ( i = 0; i < tr.refdef.num_pshadows; i++)
+	{
+		int firstDrawSurf;
+		pshadow_t *shadow = &tr.refdef.pshadows[i];
+		int j;
+
+		Com_Memset( &shadowParms, 0, sizeof( shadowParms ) );
+
+		if (glRefConfig.framebufferObject)
+		{
+			shadowParms.viewportX = 0;
+			shadowParms.viewportY = 0;
+		}
+		else
+		{
+			shadowParms.viewportX = tr.refdef.x;
+			shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE );
+		}
+		shadowParms.viewportWidth = PSHADOW_MAP_SIZE;
+		shadowParms.viewportHeight = PSHADOW_MAP_SIZE;
+		shadowParms.isPortal = qfalse;
+		shadowParms.isMirror = qfalse;
+
+		shadowParms.fovX = 90;
+		shadowParms.fovY = 90;
+
+		if (glRefConfig.framebufferObject)
+			shadowParms.targetFbo = tr.pshadowFbos[i];
+
+		shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW;
+		shadowParms.zFar = shadow->lightRadius;
+
+		VectorCopy(shadow->lightOrigin, shadowParms.or.origin);
+		
+		VectorCopy(shadow->lightViewAxis[0], shadowParms.or.axis[0]);
+		VectorCopy(shadow->lightViewAxis[1], shadowParms.or.axis[1]);
+		VectorCopy(shadow->lightViewAxis[2], shadowParms.or.axis[2]);
+
+		{
+			tr.viewCount++;
+
+			tr.viewParms = shadowParms;
+			tr.viewParms.frameSceneNum = tr.frameSceneNum;
+			tr.viewParms.frameCount = tr.frameCount;
+
+			firstDrawSurf = tr.refdef.numDrawSurfs;
+
+			tr.viewCount++;
+
+			// set viewParms.world
+			R_RotateForViewer ();
+
+			{
+				float xmin, xmax, ymin, ymax, znear, zfar;
+				viewParms_t *dest = &tr.viewParms;
+				vec3_t pop;
+
+				xmin = ymin = -shadow->viewRadius;
+				xmax = ymax = shadow->viewRadius;
+				znear = 0;
+				zfar = shadow->lightRadius;
+
+				dest->projectionMatrix[0] = 2 / (xmax - xmin);
+				dest->projectionMatrix[4] = 0;
+				dest->projectionMatrix[8] = (xmax + xmin) / (xmax - xmin);
+				dest->projectionMatrix[12] =0;
+
+				dest->projectionMatrix[1] = 0;
+				dest->projectionMatrix[5] = 2 / (ymax - ymin);
+				dest->projectionMatrix[9] = ( ymax + ymin ) / (ymax - ymin);	// normally 0
+				dest->projectionMatrix[13] = 0;
+
+				dest->projectionMatrix[2] = 0;
+				dest->projectionMatrix[6] = 0;
+				dest->projectionMatrix[10] = 2 / (zfar - znear);
+				dest->projectionMatrix[14] = 0;
+
+				dest->projectionMatrix[3] = 0;
+				dest->projectionMatrix[7] = 0;
+				dest->projectionMatrix[11] = 0;
+				dest->projectionMatrix[15] = 1;
+
+				VectorScale(dest->or.axis[1],  1.0f, dest->frustum[0].normal);
+				VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[0].normal, pop);
+				dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal);
+
+				VectorScale(dest->or.axis[1], -1.0f, dest->frustum[1].normal);
+				VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[1].normal, pop);
+				dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal);
+
+				VectorScale(dest->or.axis[2],  1.0f, dest->frustum[2].normal);
+				VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[2].normal, pop);
+				dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal);
+
+				VectorScale(dest->or.axis[2], -1.0f, dest->frustum[3].normal);
+				VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[3].normal, pop);
+				dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal);
+
+				VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal);
+				VectorMA(dest->or.origin, -shadow->lightRadius, dest->frustum[4].normal, pop);
+				dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal);
+
+				for (j = 0; j < 5; j++)
+				{
+					dest->frustum[j].type = PLANE_NON_AXIAL;
+					SetPlaneSignbits (&dest->frustum[j]);
+				}
+			}
+
+			for (j = 0; j < shadow->numEntities; j++)
+			{
+				R_AddEntitySurface(shadow->entityNums[j]);
+			}
+
+			R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf );
+
+			if (!glRefConfig.framebufferObject)
+				R_AddCapShadowmapCmd( i, -1 );
+		}
+	}
+}
+
+static float CalcSplit(float n, float f, float i, float m)
+{
+	return (n * pow(f / n, i / m) + (f - n) * i / m) / 2.0f;
+}
+
+
+void R_RenderSunShadowMaps(const refdef_t *fd, int level)
+{
+	viewParms_t		shadowParms;
+	vec4_t lightDir, lightCol;
+	vec3_t lightViewAxis[3];
+	vec3_t lightOrigin;
+	float splitZNear, splitZFar, splitBias;
+	float viewZNear, viewZFar;
+	vec3_t lightviewBounds[2];
+	qboolean lightViewIndependentOfCameraView = qfalse;
+
+	if (r_forceSun->integer == 2)
+	{
+		int scale = 32768;
+		float angle = (fd->time % scale) / (float)scale * M_PI;
+		lightDir[0] = cos(angle);
+		lightDir[1] = sin(35.0f * M_PI / 180.0f);
+		lightDir[2] = sin(angle) * cos(35.0f * M_PI / 180.0f);
+		lightDir[3] = 0.0f;
+
+		if (1) //((fd->time % (scale * 2)) < scale)
+		{
+			lightCol[0] = 
+			lightCol[1] = 
+			lightCol[2] = CLAMP(sin(angle) * 2.0f, 0.0f, 1.0f) * 2.0f;
+			lightCol[3] = 1.0f;
+		}
+		else
+		{
+			lightCol[0] = 
+			lightCol[1] = 
+			lightCol[2] = CLAMP(sin(angle) * 2.0f * 0.1f, 0.0f, 0.1f);
+			lightCol[3] = 1.0f;
+		}
+
+		VectorCopy4(lightDir, tr.refdef.sunDir);
+		VectorCopy4(lightCol, tr.refdef.sunCol);
+		VectorScale4(lightCol, 0.2f, tr.refdef.sunAmbCol);
+	}
+	else
+	{
+		VectorCopy4(tr.refdef.sunDir, lightDir);
+	}
+
+	viewZNear = r_shadowCascadeZNear->value;
+	viewZFar = r_shadowCascadeZFar->value;
+	splitBias = r_shadowCascadeZBias->value;
+
+	switch(level)
+	{
+		case 0:
+		default:
+			//splitZNear = r_znear->value;
+			//splitZFar  = 256;
+			splitZNear = viewZNear;
+			splitZFar = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias;
+			break;
+		case 1:
+			splitZNear = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias;
+			splitZFar = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias;
+			//splitZNear = 256;
+			//splitZFar  = 896;
+			break;
+		case 2:
+			splitZNear = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias;
+			splitZFar = viewZFar;
+			//splitZNear = 896;
+			//splitZFar  = 3072;
+			break;
+	}
+			
+	VectorCopy(fd->vieworg, lightOrigin);
+
+
+	// Make up a projection
+	VectorScale(lightDir, -1.0f, lightViewAxis[0]);
+
+	if (lightViewIndependentOfCameraView)
+	{
+		// Use world up as light view up
+		VectorSet(lightViewAxis[2], 0, 0, 1);
+	}
+	else if (level == 0)
+	{
+		// Level 0 tries to use a diamond texture orientation relative to camera view
+		// Use halfway between camera view forward and left for light view up
+		VectorAdd(fd->viewaxis[0], fd->viewaxis[1], lightViewAxis[2]);
+	}
+	else
+	{
+		// Use camera view up as light view up
+		VectorCopy(fd->viewaxis[2], lightViewAxis[2]);
+	}
+
+	// Check if too close to parallel to light direction
+	if (abs(DotProduct(lightViewAxis[2], lightViewAxis[0])) > 0.9f)
+	{
+		if (lightViewIndependentOfCameraView)
+		{
+			// Use world left as light view up
+			VectorSet(lightViewAxis[2], 0, 1, 0);
+		}
+		else if (level == 0)
+		{
+			// Level 0 tries to use a diamond texture orientation relative to camera view
+			// Use halfway between camera view forward and up for light view up
+			VectorAdd(fd->viewaxis[0], fd->viewaxis[2], lightViewAxis[2]);
+		}
+		else
+		{
+			// Use camera view left as light view up
+			VectorCopy(fd->viewaxis[1], lightViewAxis[2]);
+		}
+	}
+
+	// clean axes
+	CrossProduct(lightViewAxis[2], lightViewAxis[0], lightViewAxis[1]);
+	VectorNormalize(lightViewAxis[1]);
+	CrossProduct(lightViewAxis[0], lightViewAxis[1], lightViewAxis[2]);
+
+	// Create bounds for light projection using slice of view projection
+	{
+		matrix_t lightViewMatrix;
+		vec4_t point, base, lightViewPoint;
+		float lx, ly;
+
+		base[3] = 1;
+		point[3] = 1;
+		lightViewPoint[3] = 1;
+
+		Matrix16View(lightViewAxis, lightOrigin, lightViewMatrix);
+
+		ClearBounds(lightviewBounds[0], lightviewBounds[1]);
+
+		// add view near plane
+		lx = splitZNear * tan(fd->fov_x * M_PI / 360.0f);
+		ly = splitZNear * tan(fd->fov_y * M_PI / 360.0f);
+		VectorMA(fd->vieworg, splitZNear, fd->viewaxis[0], base);
+
+		VectorMA(base,   lx, fd->viewaxis[1], point);
+		VectorMA(point,  ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,  -lx, fd->viewaxis[1], point);
+		VectorMA(point,  ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,   lx, fd->viewaxis[1], point);
+		VectorMA(point, -ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,  -lx, fd->viewaxis[1], point);
+		VectorMA(point, -ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+		
+
+		// add view far plane
+		lx = splitZFar * tan(fd->fov_x * M_PI / 360.0f);
+		ly = splitZFar * tan(fd->fov_y * M_PI / 360.0f);
+		VectorMA(fd->vieworg, splitZFar, fd->viewaxis[0], base);
+
+		VectorMA(base,   lx, fd->viewaxis[1], point);
+		VectorMA(point,  ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,  -lx, fd->viewaxis[1], point);
+		VectorMA(point,  ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,   lx, fd->viewaxis[1], point);
+		VectorMA(point, -ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		VectorMA(base,  -lx, fd->viewaxis[1], point);
+		VectorMA(point, -ly, fd->viewaxis[2], point);
+		Matrix16Transform(lightViewMatrix, point, lightViewPoint);
+		AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+		if (!glRefConfig.depthClamp)
+			lightviewBounds[0][0] = lightviewBounds[1][0] - 8192;
+
+		// Moving the Light in Texel-Sized Increments
+		// from http://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx
+		//
+		if (lightViewIndependentOfCameraView)
+		{
+			float cascadeBound, worldUnitsPerTexel, invWorldUnitsPerTexel;
+
+			cascadeBound = MAX(lightviewBounds[1][0] - lightviewBounds[0][0], lightviewBounds[1][1] - lightviewBounds[0][1]);
+			cascadeBound = MAX(cascadeBound, lightviewBounds[1][2] - lightviewBounds[0][2]);
+			worldUnitsPerTexel = cascadeBound / tr.sunShadowFbo[level]->width;
+			invWorldUnitsPerTexel = 1.0f / worldUnitsPerTexel;
+
+			VectorScale(lightviewBounds[0], invWorldUnitsPerTexel, lightviewBounds[0]);
+			lightviewBounds[0][0] = floor(lightviewBounds[0][0]);
+			lightviewBounds[0][1] = floor(lightviewBounds[0][1]);
+			lightviewBounds[0][2] = floor(lightviewBounds[0][2]);
+			VectorScale(lightviewBounds[0], worldUnitsPerTexel, lightviewBounds[0]);
+
+			VectorScale(lightviewBounds[1], invWorldUnitsPerTexel, lightviewBounds[1]);
+			lightviewBounds[1][0] = floor(lightviewBounds[1][0]);
+			lightviewBounds[1][1] = floor(lightviewBounds[1][1]);
+			lightviewBounds[1][2] = floor(lightviewBounds[1][2]);
+			VectorScale(lightviewBounds[1], worldUnitsPerTexel, lightviewBounds[1]);
+		}
+
+		//ri.Printf(PRINT_ALL, "znear %f zfar %f\n", lightviewBounds[0][0], lightviewBounds[1][0]);		
+		//ri.Printf(PRINT_ALL, "fovx %f fovy %f xmin %f xmax %f ymin %f ymax %f\n", fd->fov_x, fd->fov_y, xmin, xmax, ymin, ymax);
+	}
+
+
+	{
+		int firstDrawSurf;
+
+		Com_Memset( &shadowParms, 0, sizeof( shadowParms ) );
+
+		if (glRefConfig.framebufferObject)
+		{
+			shadowParms.viewportX = 0;
+			shadowParms.viewportY = 0;
+		}
+		else
+		{
+			shadowParms.viewportX = tr.refdef.x;
+			shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.sunShadowFbo[level]->height );
+		}
+		shadowParms.viewportWidth  = tr.sunShadowFbo[level]->width;
+		shadowParms.viewportHeight = tr.sunShadowFbo[level]->height;
+		shadowParms.isPortal = qfalse;
+		shadowParms.isMirror = qfalse;
+
+		shadowParms.fovX = 90;
+		shadowParms.fovY = 90;
+
+		if (glRefConfig.framebufferObject)
+			shadowParms.targetFbo = tr.sunShadowFbo[level];
+
+		shadowParms.flags = VPF_DEPTHSHADOW | VPF_DEPTHCLAMP | VPF_ORTHOGRAPHIC;
+		shadowParms.zFar = lightviewBounds[1][0];
+
+		VectorCopy(lightOrigin, shadowParms.or.origin);
+		
+		VectorCopy(lightViewAxis[0], shadowParms.or.axis[0]);
+		VectorCopy(lightViewAxis[1], shadowParms.or.axis[1]);
+		VectorCopy(lightViewAxis[2], shadowParms.or.axis[2]);
+
+		VectorCopy(lightOrigin, shadowParms.pvsOrigin );
+
+		{
+			tr.viewCount++;
+
+			tr.viewParms = shadowParms;
+			tr.viewParms.frameSceneNum = tr.frameSceneNum;
+			tr.viewParms.frameCount = tr.frameCount;
+
+			firstDrawSurf = tr.refdef.numDrawSurfs;
+
+			tr.viewCount++;
+
+			// set viewParms.world
+			R_RotateForViewer ();
+
+			R_SetupProjectionOrtho(&tr.viewParms, lightviewBounds);
+
+			R_AddWorldSurfaces ();
+
+			R_AddPolygonSurfaces();
+
+			R_AddEntitySurfaces ();
+
+			R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf );
+		}
+
+		Matrix16Multiply(tr.viewParms.projectionMatrix, tr.viewParms.world.modelMatrix, tr.refdef.sunShadowMvp[level]);
+	}
+}

Added: trunk/code/rend2/tr_marks.c
===================================================================
--- trunk/code/rend2/tr_marks.c	                        (rev 0)
+++ trunk/code/rend2/tr_marks.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,466 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_marks.c -- polygon projection on the world polygons
+
+#include "tr_local.h"
+//#include "assert.h"
+
+#define MAX_VERTS_ON_POLY		64
+
+#define MARKER_OFFSET			0	// 1
+
+/*
+=============
+R_ChopPolyBehindPlane
+
+Out must have space for two more vertexes than in
+=============
+*/
+#define	SIDE_FRONT	0
+#define	SIDE_BACK	1
+#define	SIDE_ON		2
+static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY],
+								   int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], 
+							vec3_t normal, vec_t dist, vec_t epsilon) {
+	float		dists[MAX_VERTS_ON_POLY+4];
+	int			sides[MAX_VERTS_ON_POLY+4];
+	int			counts[3];
+	float		dot;
+	int			i, j;
+	float		*p1, *p2, *clip;
+	float		d;
+
+	// don't clip if it might overflow
+	if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) {
+		*numOutPoints = 0;
+		return;
+	}
+
+	counts[0] = counts[1] = counts[2] = 0;
+
+	// determine sides for each point
+	for ( i = 0 ; i < numInPoints ; i++ ) {
+		dot = DotProduct( inPoints[i], normal );
+		dot -= dist;
+		dists[i] = dot;
+		if ( dot > epsilon ) {
+			sides[i] = SIDE_FRONT;
+		} else if ( dot < -epsilon ) {
+			sides[i] = SIDE_BACK;
+		} else {
+			sides[i] = SIDE_ON;
+		}
+		counts[sides[i]]++;
+	}
+	sides[i] = sides[0];
+	dists[i] = dists[0];
+
+	*numOutPoints = 0;
+
+	if ( !counts[0] ) {
+		return;
+	}
+	if ( !counts[1] ) {
+		*numOutPoints = numInPoints;
+		Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) );
+		return;
+	}
+
+	for ( i = 0 ; i < numInPoints ; i++ ) {
+		p1 = inPoints[i];
+		clip = outPoints[ *numOutPoints ];
+		
+		if ( sides[i] == SIDE_ON ) {
+			VectorCopy( p1, clip );
+			(*numOutPoints)++;
+			continue;
+		}
+	
+		if ( sides[i] == SIDE_FRONT ) {
+			VectorCopy( p1, clip );
+			(*numOutPoints)++;
+			clip = outPoints[ *numOutPoints ];
+		}
+
+		if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) {
+			continue;
+		}
+			
+		// generate a split point
+		p2 = inPoints[ (i+1) % numInPoints ];
+
+		d = dists[i] - dists[i+1];
+		if ( d == 0 ) {
+			dot = 0;
+		} else {
+			dot = dists[i] / d;
+		}
+
+		// clip xyz
+
+		for (j=0 ; j<3 ; j++) {
+			clip[j] = p1[j] + dot * ( p2[j] - p1[j] );
+		}
+
+		(*numOutPoints)++;
+	}
+}
+
+/*
+=================
+R_BoxSurfaces_r
+
+=================
+*/
+void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) {
+
+	int			s, c;
+	msurface_t	*surf;
+	int *mark;
+
+	// do the tail recursion in a loop
+	while ( node->contents == -1 ) {
+		s = BoxOnPlaneSide( mins, maxs, node->plane );
+		if (s == 1) {
+			node = node->children[0];
+		} else if (s == 2) {
+			node = node->children[1];
+		} else {
+			R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir);
+			node = node->children[1];
+		}
+	}
+
+	// add the individual surfaces
+	mark = tr.world->marksurfaces + node->firstmarksurface;
+	c = node->nummarksurfaces;
+	while (c--) {
+		int *surfViewCount;
+		//
+		if (*listlength >= listsize) break;
+		//
+		surfViewCount = &tr.world->surfacesViewCount[*mark];
+		surf = tr.world->surfaces + *mark;
+		// check if the surface has NOIMPACT or NOMARKS set
+		if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) )
+			|| ( surf->shader->contentFlags & CONTENTS_FOG ) ) {
+			*surfViewCount = tr.viewCount;
+		}
+		// extra check for surfaces to avoid list overflows
+		else if (*(surf->data) == SF_FACE) {
+			// the face plane should go through the box
+			s = BoxOnPlaneSide( mins, maxs, &surf->cullinfo.plane );
+			if (s == 1 || s == 2) {
+				*surfViewCount = tr.viewCount;
+			} else if (DotProduct(surf->cullinfo.plane.normal, dir) > -0.5) {
+			// don't add faces that make sharp angles with the projection direction
+				*surfViewCount = tr.viewCount;
+			}
+		}
+		else if (*(surf->data) != SF_GRID &&
+			 *(surf->data) != SF_TRIANGLES)
+			*surfViewCount = tr.viewCount;
+		// check the viewCount because the surface may have
+		// already been added if it spans multiple leafs
+		if (*surfViewCount != tr.viewCount) {
+			*surfViewCount = tr.viewCount;
+			list[*listlength] = surf->data;
+			(*listlength)++;
+		}
+		mark++;
+	}
+}
+
+/*
+=================
+R_AddMarkFragments
+
+=================
+*/
+void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY],
+				   int numPlanes, vec3_t *normals, float *dists,
+				   int maxPoints, vec3_t pointBuffer,
+				   int maxFragments, markFragment_t *fragmentBuffer,
+				   int *returnedPoints, int *returnedFragments,
+				   vec3_t mins, vec3_t maxs) {
+	int pingPong, i;
+	markFragment_t	*mf;
+
+	// chop the surface by all the bounding planes of the to be projected polygon
+	pingPong = 0;
+
+	for ( i = 0 ; i < numPlanes ; i++ ) {
+
+		R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong],
+						   &numClipPoints, clipPoints[!pingPong],
+							normals[i], dists[i], 0.5 );
+		pingPong ^= 1;
+		if ( numClipPoints == 0 ) {
+			break;
+		}
+	}
+	// completely clipped away?
+	if ( numClipPoints == 0 ) {
+		return;
+	}
+
+	// add this fragment to the returned list
+	if ( numClipPoints + (*returnedPoints) > maxPoints ) {
+		return;	// not enough space for this polygon
+	}
+	/*
+	// all the clip points should be within the bounding box
+	for ( i = 0 ; i < numClipPoints ; i++ ) {
+		int j;
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break;
+			if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break;
+		}
+		if (j < 3) break;
+	}
+	if (i < numClipPoints) return;
+	*/
+
+	mf = fragmentBuffer + (*returnedFragments);
+	mf->firstPoint = (*returnedPoints);
+	mf->numPoints = numClipPoints;
+	Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) );
+
+	(*returnedPoints) += numClipPoints;
+	(*returnedFragments)++;
+}
+
+/*
+=================
+R_MarkFragments
+
+=================
+*/
+int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
+				   int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) {
+	int				numsurfaces, numPlanes;
+	int				i, j, k, m, n;
+	surfaceType_t	*surfaces[64];
+	vec3_t			mins, maxs;
+	int				returnedFragments;
+	int				returnedPoints;
+	vec3_t			normals[MAX_VERTS_ON_POLY+2];
+	float			dists[MAX_VERTS_ON_POLY+2];
+	vec3_t			clipPoints[2][MAX_VERTS_ON_POLY];
+	int				numClipPoints;
+	float			*v;
+	srfGridMesh_t	*cv;
+	srfTriangle_t	*tri;
+	srfVert_t		*dv;
+	vec3_t			normal;
+	vec3_t			projectionDir;
+	vec3_t			v1, v2;
+
+	if (numPoints <= 0) {
+		return 0;
+	}
+
+	//increment view count for double check prevention
+	tr.viewCount++;
+
+	//
+	VectorNormalize2( projection, projectionDir );
+	// find all the brushes that are to be considered
+	ClearBounds( mins, maxs );
+	for ( i = 0 ; i < numPoints ; i++ ) {
+		vec3_t	temp;
+
+		AddPointToBounds( points[i], mins, maxs );
+		VectorAdd( points[i], projection, temp );
+		AddPointToBounds( temp, mins, maxs );
+		// make sure we get all the leafs (also the one(s) in front of the hit surface)
+		VectorMA( points[i], -20, projectionDir, temp );
+		AddPointToBounds( temp, mins, maxs );
+	}
+
+	if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY;
+	// create the bounding planes for the to be projected polygon
+	for ( i = 0 ; i < numPoints ; i++ ) {
+		VectorSubtract(points[(i+1)%numPoints], points[i], v1);
+		VectorAdd(points[i], projection, v2);
+		VectorSubtract(points[i], v2, v2);
+		CrossProduct(v1, v2, normals[i]);
+		VectorNormalizeFast(normals[i]);
+		dists[i] = DotProduct(normals[i], points[i]);
+	}
+	// add near and far clipping planes for projection
+	VectorCopy(projectionDir, normals[numPoints]);
+	dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32;
+	VectorCopy(projectionDir, normals[numPoints+1]);
+	VectorInverse(normals[numPoints+1]);
+	dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20;
+	numPlanes = numPoints + 2;
+
+	numsurfaces = 0;
+	R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir);
+	//assert(numsurfaces <= 64);
+	//assert(numsurfaces != 64);
+
+	returnedPoints = 0;
+	returnedFragments = 0;
+
+	for ( i = 0 ; i < numsurfaces ; i++ ) {
+
+		if (*surfaces[i] == SF_GRID) {
+
+			cv = (srfGridMesh_t *) surfaces[i];
+			for ( m = 0 ; m < cv->height - 1 ; m++ ) {
+				for ( n = 0 ; n < cv->width - 1 ; n++ ) {
+					// We triangulate the grid and chop all triangles within
+					// the bounding planes of the to be projected polygon.
+					// LOD is not taken into account, not such a big deal though.
+					//
+					// It's probably much nicer to chop the grid itself and deal
+					// with this grid as a normal SF_GRID surface so LOD will
+					// be applied. However the LOD of that chopped grid must
+					// be synced with the LOD of the original curve.
+					// One way to do this; the chopped grid shares vertices with
+					// the original curve. When LOD is applied to the original
+					// curve the unused vertices are flagged. Now the chopped curve
+					// should skip the flagged vertices. This still leaves the
+					// problems with the vertices at the chopped grid edges.
+					//
+					// To avoid issues when LOD applied to "hollow curves" (like
+					// the ones around many jump pads) we now just add a 2 unit
+					// offset to the triangle vertices.
+					// The offset is added in the vertex normal vector direction
+					// so all triangles will still fit together.
+					// The 2 unit offset should avoid pretty much all LOD problems.
+
+					numClipPoints = 3;
+
+					dv = cv->verts + m * cv->width + n;
+
+					VectorCopy(dv[0].xyz, clipPoints[0][0]);
+					VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]);
+					VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+					VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
+					VectorCopy(dv[1].xyz, clipPoints[0][2]);
+					VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]);
+					// check the normal of this triangle
+					VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1);
+					VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2);
+					CrossProduct(v1, v2, normal);
+					VectorNormalizeFast(normal);
+					if (DotProduct(normal, projectionDir) < -0.1) {
+						// add the fragments of this triangle
+						R_AddMarkFragments(numClipPoints, clipPoints,
+										   numPlanes, normals, dists,
+										   maxPoints, pointBuffer,
+										   maxFragments, fragmentBuffer,
+										   &returnedPoints, &returnedFragments, mins, maxs);
+
+						if ( returnedFragments == maxFragments ) {
+							return returnedFragments;	// not enough space for more fragments
+						}
+					}
+
+					VectorCopy(dv[1].xyz, clipPoints[0][0]);
+					VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]);
+					VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+					VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
+					VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]);
+					VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]);
+					// check the normal of this triangle
+					VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1);
+					VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2);
+					CrossProduct(v1, v2, normal);
+					VectorNormalizeFast(normal);
+					if (DotProduct(normal, projectionDir) < -0.05) {
+						// add the fragments of this triangle
+						R_AddMarkFragments(numClipPoints, clipPoints,
+										   numPlanes, normals, dists,
+										   maxPoints, pointBuffer,
+										   maxFragments, fragmentBuffer,
+										   &returnedPoints, &returnedFragments, mins, maxs);
+
+						if ( returnedFragments == maxFragments ) {
+							return returnedFragments;	// not enough space for more fragments
+						}
+					}
+				}
+			}
+		}
+		else if (*surfaces[i] == SF_FACE) {
+
+			srfSurfaceFace_t *surf = ( srfSurfaceFace_t * ) surfaces[i];
+
+			// check the normal of this face
+			if (DotProduct(surf->plane.normal, projectionDir) > -0.5) {
+				continue;
+			}
+
+			for(k = 0, tri = surf->triangles; k < surf->numTriangles; k++, tri++)
+			{
+				for(j = 0; j < 3; j++)
+				{
+					v = surf->verts[tri->indexes[j]].xyz;
+					VectorMA(v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j]);
+				}
+
+				// add the fragments of this face
+				R_AddMarkFragments( 3 , clipPoints,
+								   numPlanes, normals, dists,
+								   maxPoints, pointBuffer,
+								   maxFragments, fragmentBuffer,
+								   &returnedPoints, &returnedFragments, mins, maxs);
+				if ( returnedFragments == maxFragments ) {
+					return returnedFragments;	// not enough space for more fragments
+				}
+			}
+		}
+		else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) {
+
+			srfTriangles_t *surf = (srfTriangles_t *) surfaces[i];
+
+			for(k = 0, tri = surf->triangles; k < surf->numTriangles; k++, tri++)
+			{
+				for(j = 0; j < 3; j++)
+				{
+					v = surf->verts[tri->indexes[j]].xyz;
+					VectorMA(v, MARKER_OFFSET, surf->verts[tri->indexes[j]].normal, clipPoints[0][j]);
+				}
+
+				// add the fragments of this face
+				R_AddMarkFragments(3, clipPoints,
+								   numPlanes, normals, dists,
+								   maxPoints, pointBuffer,
+								   maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs);
+				if(returnedFragments == maxFragments)
+				{
+					return returnedFragments;	// not enough space for more fragments
+				}
+			}
+		}
+	}
+	return returnedFragments;
+}
+
+
+
+
+

Added: trunk/code/rend2/tr_mesh.c
===================================================================
--- trunk/code/rend2/tr_mesh.c	                        (rev 0)
+++ trunk/code/rend2/tr_mesh.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,405 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_mesh.c: triangle model functions
+
+#include "tr_local.h"
+
+static float ProjectRadius( float r, vec3_t location )
+{
+	float pr;
+	float dist;
+	float c;
+	vec3_t	p;
+	float	projected[4];
+
+	c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin );
+	dist = DotProduct( tr.viewParms.or.axis[0], location ) - c;
+
+	if ( dist <= 0 )
+		return 0;
+
+	p[0] = 0;
+	p[1] = fabs( r );
+	p[2] = -dist;
+
+	projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + 
+		           p[1] * tr.viewParms.projectionMatrix[4] +
+				   p[2] * tr.viewParms.projectionMatrix[8] +
+				   tr.viewParms.projectionMatrix[12];
+
+	projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + 
+		           p[1] * tr.viewParms.projectionMatrix[5] +
+				   p[2] * tr.viewParms.projectionMatrix[9] +
+				   tr.viewParms.projectionMatrix[13];
+
+	projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + 
+		           p[1] * tr.viewParms.projectionMatrix[6] +
+				   p[2] * tr.viewParms.projectionMatrix[10] +
+				   tr.viewParms.projectionMatrix[14];
+
+	projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + 
+		           p[1] * tr.viewParms.projectionMatrix[7] +
+				   p[2] * tr.viewParms.projectionMatrix[11] +
+				   tr.viewParms.projectionMatrix[15];
+
+
+	pr = projected[1] / projected[3];
+
+	if ( pr > 1.0f )
+		pr = 1.0f;
+
+	return pr;
+}
+
+/*
+=============
+R_CullModel
+=============
+*/
+static int R_CullModel( mdvModel_t *model, trRefEntity_t *ent ) {
+	vec3_t		bounds[2];
+	mdvFrame_t	*oldFrame, *newFrame;
+	int			i;
+
+	// compute frame pointers
+	newFrame = model->frames + ent->e.frame;
+	oldFrame = model->frames + ent->e.oldframe;
+
+	// cull bounding sphere ONLY if this is not an upscaled entity
+	if ( !ent->e.nonNormalizedAxes )
+	{
+		if ( ent->e.frame == ent->e.oldframe )
+		{
+			switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) )
+			{
+			case CULL_OUT:
+				tr.pc.c_sphere_cull_md3_out++;
+				return CULL_OUT;
+
+			case CULL_IN:
+				tr.pc.c_sphere_cull_md3_in++;
+				return CULL_IN;
+
+			case CULL_CLIP:
+				tr.pc.c_sphere_cull_md3_clip++;
+				break;
+			}
+		}
+		else
+		{
+			int sphereCull, sphereCullB;
+
+			sphereCull  = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius );
+			if ( newFrame == oldFrame ) {
+				sphereCullB = sphereCull;
+			} else {
+				sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius );
+			}
+
+			if ( sphereCull == sphereCullB )
+			{
+				if ( sphereCull == CULL_OUT )
+				{
+					tr.pc.c_sphere_cull_md3_out++;
+					return CULL_OUT;
+				}
+				else if ( sphereCull == CULL_IN )
+				{
+					tr.pc.c_sphere_cull_md3_in++;
+					return CULL_IN;
+				}
+				else
+				{
+					tr.pc.c_sphere_cull_md3_clip++;
+				}
+			}
+		}
+	}
+	
+	// calculate a bounding box in the current coordinate system
+	for (i = 0 ; i < 3 ; i++) {
+		bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i];
+		bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i];
+	}
+
+	switch ( R_CullLocalBox( bounds ) )
+	{
+	case CULL_IN:
+		tr.pc.c_box_cull_md3_in++;
+		return CULL_IN;
+	case CULL_CLIP:
+		tr.pc.c_box_cull_md3_clip++;
+		return CULL_CLIP;
+	case CULL_OUT:
+	default:
+		tr.pc.c_box_cull_md3_out++;
+		return CULL_OUT;
+	}
+}
+
+
+/*
+=================
+R_ComputeLOD
+
+=================
+*/
+int R_ComputeLOD( trRefEntity_t *ent ) {
+	float radius;
+	float flod, lodscale;
+	float projectedRadius;
+	mdvFrame_t *frame;
+#ifdef RAVENMD4
+	mdrHeader_t *mdr;
+	mdrFrame_t *mdrframe;
+#endif
+	int lod;
+
+	if ( tr.currentModel->numLods < 2 )
+	{
+		// model has only 1 LOD level, skip computations and bias
+		lod = 0;
+	}
+	else
+	{
+		// multiple LODs exist, so compute projected bounding sphere
+		// and use that as a criteria for selecting LOD
+
+#ifdef RAVENMD4
+		if(tr.currentModel->type == MOD_MDR)
+		{
+			int frameSize;
+			mdr = (mdrHeader_t *) tr.currentModel->modelData;
+			frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]);
+			
+			mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame);
+			
+			radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]);
+		}
+		else
+#endif
+		{
+			//frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames );
+			frame = tr.currentModel->mdv[0]->frames;
+
+			frame += ent->e.frame;
+
+			radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] );
+		}
+
+		if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 )
+		{
+			lodscale = r_lodscale->value;
+			if (lodscale > 20) lodscale = 20;
+			flod = 1.0f - projectedRadius * lodscale;
+		}
+		else
+		{
+			// object intersects near view plane, e.g. view weapon
+			flod = 0;
+		}
+
+		flod *= tr.currentModel->numLods;
+		lod = ri.ftol(flod);
+
+		if ( lod < 0 )
+		{
+			lod = 0;
+		}
+		else if ( lod >= tr.currentModel->numLods )
+		{
+			lod = tr.currentModel->numLods - 1;
+		}
+	}
+
+	lod += r_lodbias->integer;
+	
+	if ( lod >= tr.currentModel->numLods )
+		lod = tr.currentModel->numLods - 1;
+	if ( lod < 0 )
+		lod = 0;
+
+	return lod;
+}
+
+/*
+=================
+R_ComputeFogNum
+
+=================
+*/
+int R_ComputeFogNum( mdvModel_t *model, trRefEntity_t *ent ) {
+	int				i, j;
+	fog_t			*fog;
+	mdvFrame_t		*mdvFrame;
+	vec3_t			localOrigin;
+
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		return 0;
+	}
+
+	// FIXME: non-normalized axis issues
+	mdvFrame = model->frames + ent->e.frame;
+	VectorAdd( ent->e.origin, mdvFrame->localOrigin, localOrigin );
+	for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+		fog = &tr.world->fogs[i];
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( localOrigin[j] - mdvFrame->radius >= fog->bounds[1][j] ) {
+				break;
+			}
+			if ( localOrigin[j] + mdvFrame->radius <= fog->bounds[0][j] ) {
+				break;
+			}
+		}
+		if ( j == 3 ) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+/*
+=================
+R_AddMD3Surfaces
+
+=================
+*/
+void R_AddMD3Surfaces( trRefEntity_t *ent ) {
+	int				i;
+	mdvModel_t		*model = NULL;
+	mdvSurface_t	*surface = NULL;
+	shader_t		*shader = NULL;
+	int				cull;
+	int				lod;
+	int				fogNum;
+	qboolean		personalModel;
+
+	// don't add third_person objects if not in a portal
+	personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal 
+	                 || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW)));
+
+	if ( ent->e.renderfx & RF_WRAP_FRAMES ) {
+		ent->e.frame %= tr.currentModel->mdv[0]->numFrames;
+		ent->e.oldframe %= tr.currentModel->mdv[0]->numFrames;
+	}
+
+	//
+	// Validate the frames so there is no chance of a crash.
+	// This will write directly into the entity structure, so
+	// when the surfaces are rendered, they don't need to be
+	// range checked again.
+	//
+	if ( (ent->e.frame >= tr.currentModel->mdv[0]->numFrames) 
+		|| (ent->e.frame < 0)
+		|| (ent->e.oldframe >= tr.currentModel->mdv[0]->numFrames)
+		|| (ent->e.oldframe < 0) ) {
+			ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n",
+				ent->e.oldframe, ent->e.frame,
+				tr.currentModel->name );
+			ent->e.frame = 0;
+			ent->e.oldframe = 0;
+	}
+
+	//
+	// compute LOD
+	//
+	lod = R_ComputeLOD( ent );
+
+	model = tr.currentModel->mdv[lod];
+
+	//
+	// cull the entire model if merged bounding box of both frames
+	// is outside the view frustum.
+	//
+	cull = R_CullModel ( model, ent );
+	if ( cull == CULL_OUT ) {
+		return;
+	}
+
+	//
+	// set up lighting now that we know we aren't culled
+	//
+	if ( !personalModel || r_shadows->integer > 1 ) {
+		R_SetupEntityLighting( &tr.refdef, ent );
+	}
+
+	//
+	// see if we are in a fog volume
+	//
+	fogNum = R_ComputeFogNum( model, ent );
+
+	//
+	// draw all surfaces
+	//
+	surface = model->surfaces;
+	for ( i = 0 ; i < model->numSurfaces ; i++ ) {
+
+		if ( ent->e.customShader ) {
+			shader = R_GetShaderByHandle( ent->e.customShader );
+		} else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) {
+			skin_t *skin;
+			int		j;
+
+			skin = R_GetSkinByHandle( ent->e.customSkin );
+
+			// match the surface name to something in the skin file
+			shader = tr.defaultShader;
+			for ( j = 0 ; j < skin->numSurfaces ; j++ ) {
+				// the names have both been lowercased
+				if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) {
+					shader = skin->surfaces[j]->shader;
+					break;
+				}
+			}
+			if (shader == tr.defaultShader) {
+				ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name);
+			}
+			else if (shader->defaultShader) {
+				ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name);
+			}
+		//} else if ( surface->numShaders <= 0 ) {
+			//shader = tr.defaultShader;
+		} else {
+			//md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders );
+			//md3Shader += ent->e.skinNum % surface->numShaders;
+			//shader = tr.shaders[ md3Shader->shaderIndex ];
+			shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ];
+		}
+
+		// don't add third_person objects if not viewing through a portal
+		if(!personalModel)
+		{
+			srfVBOMDVMesh_t *vboSurface = &model->vboSurfaces[i];
+
+			R_AddDrawSurf((void *)vboSurface, shader, fogNum, qfalse, qfalse );
+		}
+
+		surface++;
+	}
+
+}
+
+
+
+
+

Added: trunk/code/rend2/tr_model.c
===================================================================
--- trunk/code/rend2/tr_model.c	                        (rev 0)
+++ trunk/code/rend2/tr_model.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1580 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_models.c -- model loading and caching
+
+#include "tr_local.h"
+
+#define	LL(x) x=LittleLong(x)
+
+static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName);
+static qboolean R_LoadMD4(model_t *mod, void *buffer, const char *name );
+#ifdef RAVENMD4
+static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name );
+#endif
+
+/*
+====================
+R_RegisterMD3
+====================
+*/
+qhandle_t R_RegisterMD3(const char *name, model_t *mod)
+{
+	union {
+		unsigned *u;
+		void *v;
+	} buf;
+	int         size;
+	int			lod;
+	int			ident;
+	qboolean	loaded = qfalse;
+	int			numLoaded;
+	char filename[MAX_QPATH], namebuf[MAX_QPATH+20];
+	char *fext, defex[] = "md3";
+
+	numLoaded = 0;
+
+	strcpy(filename, name);
+
+	fext = strchr(filename, '.');
+	if(!fext)
+		fext = defex;
+	else
+	{
+		*fext = '\0';
+		fext++;
+	}
+
+	for (lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod--)
+	{
+		if(lod)
+			Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext);
+		else
+			Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext);
+
+		size = ri.FS_ReadFile( namebuf, &buf.v );
+		if(!buf.u)
+			continue;
+		
+		ident = LittleLong(* (unsigned *) buf.u);
+		if (ident == MD4_IDENT)
+			loaded = R_LoadMD4(mod, buf.u, name);
+		else
+		{
+			if (ident == MD3_IDENT)
+				loaded = R_LoadMD3(mod, lod, buf.u, size, name);
+			else
+				ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name);
+		}
+		
+		ri.FS_FreeFile(buf.v);
+
+		if(loaded)
+		{
+			mod->numLods++;
+			numLoaded++;
+		}
+		else
+			break;
+	}
+
+	if(numLoaded)
+	{
+		// duplicate into higher lod spots that weren't
+		// loaded, in case the user changes r_lodbias on the fly
+		for(lod--; lod >= 0; lod--)
+		{
+			mod->numLods++;
+			mod->mdv[lod] = mod->mdv[lod + 1];
+		}
+
+		return mod->index;
+	}
+
+#ifdef _DEBUG
+	ri.Printf(PRINT_WARNING,"R_RegisterMD3: couldn't load %s\n", name);
+#endif
+
+	mod->type = MOD_BAD;
+	return 0;
+}
+
+#ifdef RAVENMD4
+/*
+====================
+R_RegisterMDR
+====================
+*/
+qhandle_t R_RegisterMDR(const char *name, model_t *mod)
+{
+	union {
+		unsigned *u;
+		void *v;
+	} buf;
+	int	ident;
+	qboolean loaded = qfalse;
+	int filesize;
+
+	filesize = ri.FS_ReadFile(name, (void **) &buf.v);
+	if(!buf.u)
+	{
+		mod->type = MOD_BAD;
+		return 0;
+	}
+	
+	ident = LittleLong(*(unsigned *)buf.u);
+	if(ident == MDR_IDENT)
+		loaded = R_LoadMDR(mod, buf.u, filesize, name);
+
+	ri.FS_FreeFile (buf.v);
+	
+	if(!loaded)
+	{
+		ri.Printf(PRINT_WARNING,"R_RegisterMDR: couldn't load mdr file %s\n", name);
+		mod->type = MOD_BAD;
+		return 0;
+	}
+	
+	return mod->index;
+}
+#endif
+
+/*
+====================
+R_RegisterIQM
+====================
+*/
+qhandle_t R_RegisterIQM(const char *name, model_t *mod)
+{
+	union {
+		unsigned *u;
+		void *v;
+	} buf;
+	qboolean loaded = qfalse;
+	int filesize;
+
+	filesize = ri.FS_ReadFile(name, (void **) &buf.v);
+	if(!buf.u)
+	{
+		mod->type = MOD_BAD;
+		return 0;
+	}
+	
+	loaded = R_LoadIQM(mod, buf.u, filesize, name);
+
+	ri.FS_FreeFile (buf.v);
+	
+	if(!loaded)
+	{
+		ri.Printf(PRINT_WARNING,"R_RegisterIQM: couldn't load iqm file %s\n", name);
+		mod->type = MOD_BAD;
+		return 0;
+	}
+	
+	return mod->index;
+}
+
+
+typedef struct
+{
+	char *ext;
+	qhandle_t (*ModelLoader)( const char *, model_t * );
+} modelExtToLoaderMap_t;
+
+// Note that the ordering indicates the order of preference used
+// when there are multiple models of different formats available
+static modelExtToLoaderMap_t modelLoaders[ ] =
+{
+	{ "iqm", R_RegisterIQM },
+#ifdef RAVENMD4
+	{ "mdr", R_RegisterMDR },
+#endif
+	{ "md4", R_RegisterMD3 },
+	{ "md3", R_RegisterMD3 }
+};
+
+static int numModelLoaders = ARRAY_LEN(modelLoaders);
+
+//===============================================================================
+
+/*
+** R_GetModelByHandle
+*/
+model_t	*R_GetModelByHandle( qhandle_t index ) {
+	model_t		*mod;
+
+	// out of range gets the defualt model
+	if ( index < 1 || index >= tr.numModels ) {
+		return tr.models[0];
+	}
+
+	mod = tr.models[index];
+
+	return mod;
+}
+
+//===============================================================================
+
+/*
+** R_AllocModel
+*/
+model_t *R_AllocModel( void ) {
+	model_t		*mod;
+
+	if ( tr.numModels == MAX_MOD_KNOWN ) {
+		return NULL;
+	}
+
+	mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low );
+	mod->index = tr.numModels;
+	tr.models[tr.numModels] = mod;
+	tr.numModels++;
+
+	return mod;
+}
+
+/*
+====================
+RE_RegisterModel
+
+Loads in a model for the given name
+
+Zero will be returned if the model fails to load.
+An entry will be retained for failed models as an
+optimization to prevent disk rescanning if they are
+asked for again.
+====================
+*/
+qhandle_t RE_RegisterModel( const char *name ) {
+	model_t		*mod;
+	qhandle_t	hModel;
+	qboolean	orgNameFailed = qfalse;
+	int			orgLoader = -1;
+	int			i;
+	char		localName[ MAX_QPATH ];
+	const char	*ext;
+	char		altName[ MAX_QPATH ];
+
+	if ( !name || !name[0] ) {
+		ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" );
+		return 0;
+	}
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+	//
+	// search the currently loaded models
+	//
+	for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) {
+		mod = tr.models[hModel];
+		if ( !strcmp( mod->name, name ) ) {
+			if( mod->type == MOD_BAD ) {
+				return 0;
+			}
+			return hModel;
+		}
+	}
+
+	// allocate a new model_t
+
+	if ( ( mod = R_AllocModel() ) == NULL ) {
+		ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name);
+		return 0;
+	}
+
+	// only set the name after the model has been successfully loaded
+	Q_strncpyz( mod->name, name, sizeof( mod->name ) );
+
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	mod->type = MOD_BAD;
+	mod->numLods = 0;
+
+	//
+	// load the files
+	//
+	Q_strncpyz( localName, name, MAX_QPATH );
+
+	ext = COM_GetExtension( localName );
+
+	if( *ext )
+	{
+		// Look for the correct loader and use it
+		for( i = 0; i < numModelLoaders; i++ )
+		{
+			if( !Q_stricmp( ext, modelLoaders[ i ].ext ) )
+			{
+				// Load
+				hModel = modelLoaders[ i ].ModelLoader( localName, mod );
+				break;
+			}
+		}
+
+		// A loader was found
+		if( i < numModelLoaders )
+		{
+			if( !hModel )
+			{
+				// Loader failed, most likely because the file isn't there;
+				// try again without the extension
+				orgNameFailed = qtrue;
+				orgLoader = i;
+				COM_StripExtension( name, localName, MAX_QPATH );
+			}
+			else
+			{
+				// Something loaded
+				return mod->index;
+			}
+		}
+	}
+
+	// Try and find a suitable match using all
+	// the model formats supported
+	for( i = 0; i < numModelLoaders; i++ )
+	{
+		if (i == orgLoader)
+			continue;
+
+		Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext );
+
+		// Load
+		hModel = modelLoaders[ i ].ModelLoader( altName, mod );
+
+		if( hModel )
+		{
+			if( orgNameFailed )
+			{
+				ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n",
+						name, altName );
+			}
+
+			break;
+		}
+	}
+
+	return hModel;
+}
+
+/*
+=================
+R_LoadMD3
+=================
+*/
+static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName)
+{
+	int             f, i, j, k;
+
+	md3Header_t    *md3Model;
+	md3Frame_t     *md3Frame;
+	md3Surface_t   *md3Surf;
+	md3Shader_t    *md3Shader;
+	md3Triangle_t  *md3Tri;
+	md3St_t        *md3st;
+	md3XyzNormal_t *md3xyz;
+	md3Tag_t       *md3Tag;
+
+	mdvModel_t     *mdvModel;
+	mdvFrame_t     *frame;
+	mdvSurface_t   *surf;//, *surface;
+	int            *shaderIndex;
+	srfTriangle_t  *tri;
+	mdvVertex_t    *v;
+	mdvSt_t        *st;
+	mdvTag_t       *tag;
+	mdvTagName_t   *tagName;
+
+	int             version;
+	int             size;
+
+	md3Model = (md3Header_t *) buffer;
+
+	version = LittleLong(md3Model->version);
+	if(version != MD3_VERSION)
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", modName, version, MD3_VERSION);
+		return qfalse;
+	}
+
+	mod->type = MOD_MESH;
+	size = LittleLong(md3Model->ofsEnd);
+	mod->dataSize += size;
+	mdvModel = mod->mdv[lod] = ri.Hunk_Alloc(sizeof(mdvModel_t), h_low);
+
+//  Com_Memcpy(mod->md3[lod], buffer, LittleLong(md3Model->ofsEnd));
+
+	LL(md3Model->ident);
+	LL(md3Model->version);
+	LL(md3Model->numFrames);
+	LL(md3Model->numTags);
+	LL(md3Model->numSurfaces);
+	LL(md3Model->ofsFrames);
+	LL(md3Model->ofsTags);
+	LL(md3Model->ofsSurfaces);
+	LL(md3Model->ofsEnd);
+
+	if(md3Model->numFrames < 1)
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has no frames\n", modName);
+		return qfalse;
+	}
+
+	// swap all the frames
+	mdvModel->numFrames = md3Model->numFrames;
+	mdvModel->frames = frame = ri.Hunk_Alloc(sizeof(*frame) * md3Model->numFrames, h_low);
+
+	md3Frame = (md3Frame_t *) ((byte *) md3Model + md3Model->ofsFrames);
+	for(i = 0; i < md3Model->numFrames; i++, frame++, md3Frame++)
+	{
+		frame->radius = LittleFloat(md3Frame->radius);
+		for(j = 0; j < 3; j++)
+		{
+			frame->bounds[0][j] = LittleFloat(md3Frame->bounds[0][j]);
+			frame->bounds[1][j] = LittleFloat(md3Frame->bounds[1][j]);
+			frame->localOrigin[j] = LittleFloat(md3Frame->localOrigin[j]);
+		}
+	}
+
+	// swap all the tags
+	mdvModel->numTags = md3Model->numTags;
+	mdvModel->tags = tag = ri.Hunk_Alloc(sizeof(*tag) * (md3Model->numTags * md3Model->numFrames), h_low);
+
+	md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags);
+	for(i = 0; i < md3Model->numTags * md3Model->numFrames; i++, tag++, md3Tag++)
+	{
+		for(j = 0; j < 3; j++)
+		{
+			tag->origin[j] = LittleFloat(md3Tag->origin[j]);
+			tag->axis[0][j] = LittleFloat(md3Tag->axis[0][j]);
+			tag->axis[1][j] = LittleFloat(md3Tag->axis[1][j]);
+			tag->axis[2][j] = LittleFloat(md3Tag->axis[2][j]);
+		}
+	}
+
+
+	mdvModel->tagNames = tagName = ri.Hunk_Alloc(sizeof(*tagName) * (md3Model->numTags), h_low);
+
+	md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags);
+	for(i = 0; i < md3Model->numTags; i++, tagName++, md3Tag++)
+	{
+		Q_strncpyz(tagName->name, md3Tag->name, sizeof(tagName->name));
+	}
+
+	// swap all the surfaces
+	mdvModel->numSurfaces = md3Model->numSurfaces;
+	mdvModel->surfaces = surf = ri.Hunk_Alloc(sizeof(*surf) * md3Model->numSurfaces, h_low);
+
+	md3Surf = (md3Surface_t *) ((byte *) md3Model + md3Model->ofsSurfaces);
+	for(i = 0; i < md3Model->numSurfaces; i++)
+	{
+		LL(md3Surf->ident);
+		LL(md3Surf->flags);
+		LL(md3Surf->numFrames);
+		LL(md3Surf->numShaders);
+		LL(md3Surf->numTriangles);
+		LL(md3Surf->ofsTriangles);
+		LL(md3Surf->numVerts);
+		LL(md3Surf->ofsShaders);
+		LL(md3Surf->ofsSt);
+		LL(md3Surf->ofsXyzNormals);
+		LL(md3Surf->ofsEnd);
+
+		if(md3Surf->numVerts > SHADER_MAX_VERTEXES)
+		{
+			ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
+					 modName, SHADER_MAX_VERTEXES, md3Surf->numVerts);
+			return qfalse;
+		}
+		if(md3Surf->numTriangles * 3 > SHADER_MAX_INDEXES)
+		{
+			ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
+					 modName, SHADER_MAX_INDEXES / 3, md3Surf->numTriangles);
+			return qfalse;
+		}
+
+		// change to surface identifier
+		surf->surfaceType = SF_MDV;
+
+		// give pointer to model for Tess_SurfaceMDX
+		surf->model = mdvModel;
+
+		// copy surface name
+		Q_strncpyz(surf->name, md3Surf->name, sizeof(surf->name));
+
+		// lowercase the surface name so skin compares are faster
+		Q_strlwr(surf->name);
+
+		// strip off a trailing _1 or _2
+		// this is a crutch for q3data being a mess
+		j = strlen(surf->name);
+		if(j > 2 && surf->name[j - 2] == '_')
+		{
+			surf->name[j - 2] = 0;
+		}
+
+		// register the shaders
+		surf->numShaderIndexes = md3Surf->numShaders;
+		surf->shaderIndexes = shaderIndex = ri.Hunk_Alloc(sizeof(*shaderIndex) * md3Surf->numShaders, h_low);
+
+		md3Shader = (md3Shader_t *) ((byte *) md3Surf + md3Surf->ofsShaders);
+		for(j = 0; j < md3Surf->numShaders; j++, shaderIndex++, md3Shader++)
+		{
+			shader_t       *sh;
+
+			sh = R_FindShader(md3Shader->name, LIGHTMAP_NONE, qtrue);
+			if(sh->defaultShader)
+			{
+				*shaderIndex = 0;
+			}
+			else
+			{
+				*shaderIndex = sh->index;
+			}
+		}
+
+		// swap all the triangles
+		surf->numTriangles = md3Surf->numTriangles;
+		surf->triangles = tri = ri.Hunk_Alloc(sizeof(*tri) * md3Surf->numTriangles, h_low);
+
+		md3Tri = (md3Triangle_t *) ((byte *) md3Surf + md3Surf->ofsTriangles);
+		for(j = 0; j < md3Surf->numTriangles; j++, tri++, md3Tri++)
+		{
+			tri->indexes[0] = LittleLong(md3Tri->indexes[0]);
+			tri->indexes[1] = LittleLong(md3Tri->indexes[1]);
+			tri->indexes[2] = LittleLong(md3Tri->indexes[2]);
+		}
+
+		R_CalcSurfaceTriangleNeighbors(surf->numTriangles, surf->triangles);
+
+		// swap all the XyzNormals
+		surf->numVerts = md3Surf->numVerts;
+		surf->verts = v = ri.Hunk_Alloc(sizeof(*v) * (md3Surf->numVerts * md3Surf->numFrames), h_low);
+
+		md3xyz = (md3XyzNormal_t *) ((byte *) md3Surf + md3Surf->ofsXyzNormals);
+		for(j = 0; j < md3Surf->numVerts * md3Surf->numFrames; j++, md3xyz++, v++)
+		{
+			unsigned lat, lng;
+			unsigned short normal;
+
+			v->xyz[0] = LittleShort(md3xyz->xyz[0]) * MD3_XYZ_SCALE;
+			v->xyz[1] = LittleShort(md3xyz->xyz[1]) * MD3_XYZ_SCALE;
+			v->xyz[2] = LittleShort(md3xyz->xyz[2]) * MD3_XYZ_SCALE;
+
+			normal = LittleShort(md3xyz->normal);
+
+			lat = ( normal >> 8 ) & 0xff;
+			lng = ( normal & 0xff );
+			lat *= (FUNCTABLE_SIZE/256);
+			lng *= (FUNCTABLE_SIZE/256);
+
+			// decode X as cos( lat ) * sin( long )
+			// decode Y as sin( lat ) * sin( long )
+			// decode Z as cos( long )
+
+			v->normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			v->normal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			v->normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+		}
+
+		// swap all the ST
+		surf->st = st = ri.Hunk_Alloc(sizeof(*st) * md3Surf->numVerts, h_low);
+
+		md3st = (md3St_t *) ((byte *) md3Surf + md3Surf->ofsSt);
+		for(j = 0; j < md3Surf->numVerts; j++, md3st++, st++)
+		{
+			st->st[0] = LittleFloat(md3st->st[0]);
+			st->st[1] = LittleFloat(md3st->st[1]);
+		}
+
+#ifdef USE_VERT_TANGENT_SPACE
+		// calc tangent spaces
+		{
+			// Valgrind complaints: Conditional jump or move depends on uninitialised value(s)
+			// So lets Initialize them.
+			const float    *v0 = NULL, *v1 = NULL, *v2 = NULL;
+			const float    *t0 = NULL, *t1 = NULL, *t2 = NULL;
+			vec3_t          tangent = { 0, 0, 0 };
+			vec3_t          bitangent = { 0, 0, 0 };
+			vec3_t          normal = { 0, 0, 0 };
+
+			for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
+			{
+				VectorClear(v->tangent);
+				VectorClear(v->bitangent);
+				if (r_recalcMD3Normals->integer)
+					VectorClear(v->normal);
+			}
+
+			for(f = 0; f < mdvModel->numFrames; f++)
+			{
+				for(j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++)
+				{
+					v0 = surf->verts[surf->numVerts * f + tri->indexes[0]].xyz;
+					v1 = surf->verts[surf->numVerts * f + tri->indexes[1]].xyz;
+					v2 = surf->verts[surf->numVerts * f + tri->indexes[2]].xyz;
+
+					t0 = surf->st[tri->indexes[0]].st;
+					t1 = surf->st[tri->indexes[1]].st;
+					t2 = surf->st[tri->indexes[2]].st;
+
+					if (!r_recalcMD3Normals->integer)
+						VectorCopy(v->normal, normal);
+					else
+						VectorClear(normal);
+
+					#if 1
+					R_CalcTangentSpace(tangent, bitangent, normal, v0, v1, v2, t0, t1, t2);
+					#else
+					R_CalcNormalForTriangle(normal, v0, v1, v2);
+					R_CalcTangentsForTriangle(tangent, bitangent, v0, v1, v2, t0, t1, t2);
+					#endif
+
+					for(k = 0; k < 3; k++)
+					{
+						float          *v;
+
+						v = surf->verts[surf->numVerts * f + tri->indexes[k]].tangent;
+						VectorAdd(v, tangent, v);
+
+						v = surf->verts[surf->numVerts * f + tri->indexes[k]].bitangent;
+						VectorAdd(v, bitangent, v);
+
+						if (r_recalcMD3Normals->integer)
+						{
+							v = surf->verts[surf->numVerts * f + tri->indexes[k]].normal;
+							VectorAdd(v, normal, v);
+						}
+					}
+				}
+			}
+
+			for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
+			{
+				VectorNormalize(v->tangent);
+				VectorNormalize(v->bitangent);
+				VectorNormalize(v->normal);
+			}
+		}
+#endif
+
+		// find the next surface
+		md3Surf = (md3Surface_t *) ((byte *) md3Surf + md3Surf->ofsEnd);
+		surf++;
+	}
+
+	{
+		srfVBOMDVMesh_t *vboSurf;
+
+		mdvModel->numVBOSurfaces = mdvModel->numSurfaces;
+		mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low);
+
+		vboSurf = mdvModel->vboSurfaces;
+		surf = mdvModel->surfaces;
+		for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++)
+		{
+			vec3_t *verts;
+			vec3_t *normals;
+			vec2_t *texcoords;
+#ifdef USE_VERT_TANGENT_SPACE
+			vec3_t *tangents;
+			vec3_t *bitangents;
+#endif
+
+			byte *data;
+			int dataSize;
+
+			int ofs_xyz, ofs_normal, ofs_st;
+#ifdef USE_VERT_TANGENT_SPACE
+			int ofs_tangent, ofs_bitangent;
+#endif
+
+			dataSize = 0;
+
+			ofs_xyz = dataSize;
+			dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*verts);
+
+			ofs_normal = dataSize;
+			dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*normals);
+
+#ifdef USE_VERT_TANGENT_SPACE
+			ofs_tangent = dataSize;
+			dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*tangents);
+
+			ofs_bitangent = dataSize;
+			dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*bitangents);
+#endif
+
+			ofs_st = dataSize;
+			dataSize += surf->numVerts * sizeof(*texcoords);
+
+			data = ri.Malloc(dataSize);
+
+			verts =      (void *)(data + ofs_xyz);
+			normals =    (void *)(data + ofs_normal);
+#ifdef USE_VERT_TANGENT_SPACE
+			tangents =   (void *)(data + ofs_tangent);
+			bitangents = (void *)(data + ofs_bitangent);
+#endif
+			texcoords =  (void *)(data + ofs_st);
+		
+			v = surf->verts;
+			for ( j = 0; j < surf->numVerts * mdvModel->numFrames ; j++, v++ )
+			{
+				VectorCopy(v->xyz,       verts[j]);
+				VectorCopy(v->normal,    normals[j]);
+#ifdef USE_VERT_TANGENT_SPACE
+				VectorCopy(v->tangent,   tangents[j]);
+				VectorCopy(v->bitangent, bitangents[j]);
+#endif
+			}
+
+			st = surf->st;
+			for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
+				texcoords[j][0] = st->st[0];
+				texcoords[j][1] = st->st[1];
+			}
+
+			vboSurf->surfaceType = SF_VBO_MDVMESH;
+			vboSurf->mdvModel = mdvModel;
+			vboSurf->mdvSurface = surf;
+			vboSurf->numIndexes = surf->numTriangles * 3;
+			vboSurf->numVerts = surf->numVerts;
+			vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC);
+
+			vboSurf->vbo->ofs_xyz       = ofs_xyz;
+			vboSurf->vbo->ofs_normal    = ofs_normal;
+#ifdef USE_VERT_TANGENT_SPACE
+			vboSurf->vbo->ofs_tangent   = ofs_tangent;
+			vboSurf->vbo->ofs_bitangent = ofs_bitangent;
+#endif
+			vboSurf->vbo->ofs_st        = ofs_st;
+
+			vboSurf->vbo->stride_xyz       = sizeof(*verts);
+			vboSurf->vbo->stride_normal    = sizeof(*normals);
+#ifdef USE_VERT_TANGENT_SPACE
+			vboSurf->vbo->stride_tangent   = sizeof(*tangents);
+			vboSurf->vbo->stride_bitangent = sizeof(*bitangents);
+#endif
+			vboSurf->vbo->stride_st        = sizeof(*st);
+
+			vboSurf->vbo->size_xyz    = sizeof(*verts) * surf->numVerts;
+			vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts;
+
+			ri.Free(data);
+
+			vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numTriangles, surf->triangles, VBO_USAGE_STATIC);
+		}
+	}
+
+	return qtrue;
+}
+
+
+#ifdef RAVENMD4
+
+/*
+=================
+R_LoadMDR
+=================
+*/
+static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) 
+{
+	int					i, j, k, l;
+	mdrHeader_t			*pinmodel, *mdr;
+	mdrFrame_t			*frame;
+	mdrLOD_t			*lod, *curlod;
+	mdrSurface_t			*surf, *cursurf;
+	mdrTriangle_t			*tri, *curtri;
+	mdrVertex_t			*v, *curv;
+	mdrWeight_t			*weight, *curweight;
+	mdrTag_t			*tag, *curtag;
+	int					size;
+	shader_t			*sh;
+
+	pinmodel = (mdrHeader_t *)buffer;
+
+	pinmodel->version = LittleLong(pinmodel->version);
+	if (pinmodel->version != MDR_VERSION) 
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION);
+		return qfalse;
+	}
+
+	size = LittleLong(pinmodel->ofsEnd);
+	
+	if(size > filesize)
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name);
+		return qfalse;
+	}
+	
+	mod->type = MOD_MDR;
+
+	LL(pinmodel->numFrames);
+	LL(pinmodel->numBones);
+	LL(pinmodel->ofsFrames);
+
+	// This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame
+	// over and over again, we'll uncompress it in this function already, so we must adjust the size of the target md4.
+	if(pinmodel->ofsFrames < 0)
+	{
+		// mdrFrame_t is larger than mdrCompFrame_t:
+		size += pinmodel->numFrames * sizeof(frame->name);
+		// now add enough space for the uncompressed bones.
+		size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t)));
+	}
+	
+	// simple bounds check
+	if(pinmodel->numBones < 0 ||
+		sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size)
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+		return qfalse;
+	}
+
+	mod->dataSize += size;
+	mod->modelData = mdr = ri.Hunk_Alloc( size, h_low );
+
+	// Copy all the values over from the file and fix endian issues in the process, if necessary.
+	
+	mdr->ident = LittleLong(pinmodel->ident);
+	mdr->version = pinmodel->version;	// Don't need to swap byte order on this one, we already did above.
+	Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name));
+	mdr->numFrames = pinmodel->numFrames;
+	mdr->numBones = pinmodel->numBones;
+	mdr->numLODs = LittleLong(pinmodel->numLODs);
+	mdr->numTags = LittleLong(pinmodel->numTags);
+	// We don't care about the other offset values, we'll generate them ourselves while loading.
+
+	mod->numLods = mdr->numLODs;
+
+	if ( mdr->numFrames < 1 ) 
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name);
+		return qfalse;
+	}
+
+	/* The first frame will be put into the first free space after the header */
+	frame = (mdrFrame_t *)(mdr + 1);
+	mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr);
+		
+	if (pinmodel->ofsFrames < 0)
+	{
+		mdrCompFrame_t *cframe;
+				
+		// compressed model...				
+		cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames);
+		
+		for(i = 0; i < mdr->numFrames; i++)
+		{
+			for(j = 0; j < 3; j++)
+			{
+				frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]);
+				frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]);
+				frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]);
+			}
+
+			frame->radius = LittleFloat(cframe->radius);
+			frame->name[0] = '\0';	// No name supplied in the compressed version.
+			
+			for(j = 0; j < mdr->numBones; j++)
+			{
+				for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++)
+				{
+					// Do swapping for the uncompressing functions. They seem to use shorts
+					// values only, so I assume this will work. Never tested it on other
+					// platforms, though.
+					
+					((unsigned short *)(cframe->bones[j].Comp))[k] =
+						LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] );
+				}
+				
+				/* Now do the actual uncompressing */
+				MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp);
+			}
+			
+			// Next Frame...
+			cframe = (mdrCompFrame_t *) &cframe->bones[j];
+			frame = (mdrFrame_t *) &frame->bones[j];
+		}
+	}
+	else
+	{
+		mdrFrame_t *curframe;
+		
+		// uncompressed model...
+		//
+    
+		curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames);
+		
+		// swap all the frames
+		for ( i = 0 ; i < mdr->numFrames ; i++) 
+		{
+			for(j = 0; j < 3; j++)
+			{
+				frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]);
+				frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]);
+				frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]);
+			}
+			
+			frame->radius = LittleFloat(curframe->radius);
+			Q_strncpyz(frame->name, curframe->name, sizeof(frame->name));
+			
+			for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++) 
+			{
+				((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] );
+			}
+			
+			curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones];
+			frame = (mdrFrame_t *) &frame->bones[mdr->numBones];
+		}
+	}
+	
+	// frame should now point to the first free address after all frames.
+	lod = (mdrLOD_t *) frame;
+	mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr);
+	
+	curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs));
+		
+	// swap all the LOD's
+	for ( l = 0 ; l < mdr->numLODs ; l++)
+	{
+		// simple bounds check
+		if((byte *) (lod + 1) > (byte *) mdr + size)
+		{
+			ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+			return qfalse;
+		}
+
+		lod->numSurfaces = LittleLong(curlod->numSurfaces);
+		
+		// swap all the surfaces
+		surf = (mdrSurface_t *) (lod + 1);
+		lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod);
+		cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces));
+		
+		for ( i = 0 ; i < lod->numSurfaces ; i++)
+		{
+			// simple bounds check
+			if((byte *) (surf + 1) > (byte *) mdr + size)
+			{
+				ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+				return qfalse;
+			}
+
+			// first do some copying stuff
+			
+			surf->ident = SF_MDR;
+			Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name));
+			Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader));
+			
+			surf->ofsHeader = (byte *) mdr - (byte *) surf;
+			
+			surf->numVerts = LittleLong(cursurf->numVerts);
+			surf->numTriangles = LittleLong(cursurf->numTriangles);
+			// numBoneReferences and BoneReferences generally seem to be unused
+			
+			// now do the checks that may fail.
+			if ( surf->numVerts > SHADER_MAX_VERTEXES ) 
+			{
+				ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on a surface (%i).\n",
+					  mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+				return qfalse;
+			}
+			if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) 
+			{
+				ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on a surface (%i).\n",
+					  mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+				return qfalse;
+			}
+			// lowercase the surface name so skin compares are faster
+			Q_strlwr( surf->name );
+
+			// register the shaders
+			sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue);
+			if ( sh->defaultShader ) {
+				surf->shaderIndex = 0;
+			} else {
+				surf->shaderIndex = sh->index;
+			}
+			
+			// now copy the vertexes.
+			v = (mdrVertex_t *) (surf + 1);
+			surf->ofsVerts = (int)((byte *) v - (byte *) surf);
+			curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts));
+			
+			for(j = 0; j < surf->numVerts; j++)
+			{
+				LL(curv->numWeights);
+			
+				// simple bounds check
+				if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size)
+				{
+					ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+					return qfalse;
+				}
+
+				v->normal[0] = LittleFloat(curv->normal[0]);
+				v->normal[1] = LittleFloat(curv->normal[1]);
+				v->normal[2] = LittleFloat(curv->normal[2]);
+				
+				v->texCoords[0] = LittleFloat(curv->texCoords[0]);
+				v->texCoords[1] = LittleFloat(curv->texCoords[1]);
+				
+				v->numWeights = curv->numWeights;
+				weight = &v->weights[0];
+				curweight = &curv->weights[0];
+				
+				// Now copy all the weights
+				for(k = 0; k < v->numWeights; k++)
+				{
+					weight->boneIndex = LittleLong(curweight->boneIndex);
+					weight->boneWeight = LittleFloat(curweight->boneWeight);
+					
+					weight->offset[0] = LittleFloat(curweight->offset[0]);
+					weight->offset[1] = LittleFloat(curweight->offset[1]);
+					weight->offset[2] = LittleFloat(curweight->offset[2]);
+					
+					weight++;
+					curweight++;
+				}
+				
+				v = (mdrVertex_t *) weight;
+				curv = (mdrVertex_t *) curweight;
+			}
+						
+			// we know the offset to the triangles now:
+			tri = (mdrTriangle_t *) v;
+			surf->ofsTriangles = (int)((byte *) tri - (byte *) surf);
+			curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles));
+			
+			// simple bounds check
+			if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size)
+			{
+				ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+				return qfalse;
+			}
+
+			for(j = 0; j < surf->numTriangles; j++)
+			{
+				tri->indexes[0] = LittleLong(curtri->indexes[0]);
+				tri->indexes[1] = LittleLong(curtri->indexes[1]);
+				tri->indexes[2] = LittleLong(curtri->indexes[2]);
+				
+				tri++;
+				curtri++;
+			}
+			
+			// tri now points to the end of the surface.
+			surf->ofsEnd = (byte *) tri - (byte *) surf;
+			surf = (mdrSurface_t *) tri;
+
+			// find the next surface.
+			cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd));
+		}
+
+		// surf points to the next lod now.
+		lod->ofsEnd = (int)((byte *) surf - (byte *) lod);
+		lod = (mdrLOD_t *) surf;
+
+		// find the next LOD.
+		curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd));
+	}
+	
+	// lod points to the first tag now, so update the offset too.
+	tag = (mdrTag_t *) lod;
+	mdr->ofsTags = (int)((byte *) tag - (byte *) mdr);
+	curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags));
+
+	// simple bounds check
+	if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size)
+	{
+		ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+		return qfalse;
+	}
+	
+	for (i = 0 ; i < mdr->numTags ; i++)
+	{
+		tag->boneIndex = LittleLong(curtag->boneIndex);
+		Q_strncpyz(tag->name, curtag->name, sizeof(tag->name));
+		
+		tag++;
+		curtag++;
+	}
+	
+	// And finally we know the real offset to the end.
+	mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr);
+
+	// phew! we're done.
+	
+	return qtrue;
+}
+#endif
+
+/*
+=================
+R_LoadMD4
+=================
+*/
+
+static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) {
+	int					i, j, k, lodindex;
+	md4Header_t			*pinmodel, *md4;
+    md4Frame_t			*frame;
+	md4LOD_t			*lod;
+	md4Surface_t		*surf;
+	md4Triangle_t		*tri;
+	md4Vertex_t			*v;
+	int					version;
+	int					size;
+	shader_t			*sh;
+	int					frameSize;
+
+	pinmodel = (md4Header_t *)buffer;
+
+	version = LittleLong (pinmodel->version);
+	if (version != MD4_VERSION) {
+		ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n",
+				 mod_name, version, MD4_VERSION);
+		return qfalse;
+	}
+
+	mod->type = MOD_MD4;
+	size = LittleLong(pinmodel->ofsEnd);
+	mod->dataSize += size;
+	mod->modelData = md4 = ri.Hunk_Alloc( size, h_low );
+
+	Com_Memcpy(md4, buffer, size);
+
+    LL(md4->ident);
+    LL(md4->version);
+    LL(md4->numFrames);
+    LL(md4->numBones);
+    LL(md4->numLODs);
+    LL(md4->ofsFrames);
+    LL(md4->ofsLODs);
+    md4->ofsEnd = size;
+
+	if ( md4->numFrames < 1 ) {
+		ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name );
+		return qfalse;
+	}
+
+    // we don't need to swap tags in the renderer, they aren't used
+    
+	// swap all the frames
+	frameSize = (size_t)( &((md4Frame_t *)0)->bones[ md4->numBones ] );
+    for ( i = 0 ; i < md4->numFrames ; i++) {
+	    frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize );
+    	frame->radius = LittleFloat( frame->radius );
+        for ( j = 0 ; j < 3 ; j++ ) {
+            frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
+            frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
+	    	frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
+        }
+		for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) {
+			((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] );
+		}
+	}
+
+	// swap all the LOD's
+	lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs );
+	for ( lodindex = 0 ; lodindex < md4->numLODs ; lodindex++ ) {
+
+		// swap all the surfaces
+		surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces );
+		for ( i = 0 ; i < lod->numSurfaces ; i++) {
+			LL(surf->ident);
+			LL(surf->numTriangles);
+			LL(surf->ofsTriangles);
+			LL(surf->numVerts);
+			LL(surf->ofsVerts);
+			LL(surf->ofsEnd);
+			
+			if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
+				ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i verts on a surface (%i).\n",
+					mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+				return qfalse;
+			}
+			if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
+				ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i triangles on a surface (%i).\n",
+					mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+				return qfalse;
+			}
+
+			// change to surface identifier
+			surf->ident = SF_MD4;
+
+			// lowercase the surface name so skin compares are faster
+			Q_strlwr( surf->name );
+		
+			// register the shaders
+			sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue );
+			if ( sh->defaultShader ) {
+				surf->shaderIndex = 0;
+			} else {
+				surf->shaderIndex = sh->index;
+			}
+
+			// swap all the triangles
+			tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
+			for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
+				LL(tri->indexes[0]);
+				LL(tri->indexes[1]);
+				LL(tri->indexes[2]);
+			}
+
+			// swap all the vertexes
+			// FIXME
+			// This makes TFC's skeletons work.  Shouldn't be necessary anymore, but left
+			// in for reference.
+			//v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts + 12);
+			v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts);
+			for ( j = 0 ; j < surf->numVerts ; j++ ) {
+				v->normal[0] = LittleFloat( v->normal[0] );
+				v->normal[1] = LittleFloat( v->normal[1] );
+				v->normal[2] = LittleFloat( v->normal[2] );
+
+				v->texCoords[0] = LittleFloat( v->texCoords[0] );
+				v->texCoords[1] = LittleFloat( v->texCoords[1] );
+
+				v->numWeights = LittleLong( v->numWeights );
+
+				for ( k = 0 ; k < v->numWeights ; k++ ) {
+					v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex );
+					v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight );
+				   v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] );
+				   v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] );
+				   v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] );
+				}
+				// FIXME
+				// This makes TFC's skeletons work.  Shouldn't be necessary anymore, but left
+				// in for reference.
+				//v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 );
+				v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights]);
+			}
+
+			// find the next surface
+			surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd );
+		}
+
+		// find the next LOD
+		lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd );
+	}
+
+	return qtrue;
+}
+
+
+
+//=============================================================================
+
+/*
+** RE_BeginRegistration
+*/
+void RE_BeginRegistration( glconfig_t *glconfigOut ) {
+
+	R_Init();
+
+	*glconfigOut = glConfig;
+
+	R_SyncRenderThread();
+
+	tr.visIndex = 0;
+	memset(tr.visClusters, -2, sizeof(tr.visClusters));	// force markleafs to regenerate
+
+	R_ClearFlares();
+	RE_ClearScene();
+
+	tr.registered = qtrue;
+
+	// NOTE: this sucks, for some reason the first stretch pic is never drawn
+	// without this we'd see a white flash on a level load because the very
+	// first time the level shot would not be drawn
+//	RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0);
+}
+
+//=============================================================================
+
+/*
+===============
+R_ModelInit
+===============
+*/
+void R_ModelInit( void ) {
+	model_t		*mod;
+
+	// leave a space for NULL model
+	tr.numModels = 0;
+
+	mod = R_AllocModel();
+	mod->type = MOD_BAD;
+}
+
+
+/*
+================
+R_Modellist_f
+================
+*/
+void R_Modellist_f( void ) {
+	int		i, j;
+	model_t	*mod;
+	int		total;
+	int		lods;
+
+	total = 0;
+	for ( i = 1 ; i < tr.numModels; i++ ) {
+		mod = tr.models[i];
+		lods = 1;
+		for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) {
+			if ( mod->mdv[j] && mod->mdv[j] != mod->mdv[j-1] ) {
+				lods++;
+			}
+		}
+		ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name );
+		total += mod->dataSize;
+	}
+	ri.Printf( PRINT_ALL, "%8i : Total models\n", total );
+
+#if	0		// not working right with new hunk
+	if ( tr.world ) {
+		ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name );
+	}
+#endif
+}
+
+
+//=============================================================================
+
+
+/*
+================
+R_GetTag
+================
+*/
+static mdvTag_t *R_GetTag( mdvModel_t *mod, int frame, const char *_tagName ) {
+	int             i;
+	mdvTag_t       *tag;
+	mdvTagName_t   *tagName;
+
+	if ( frame >= mod->numFrames ) {
+		// it is possible to have a bad frame while changing models, so don't error
+		frame = mod->numFrames - 1;
+	}
+
+	tag = mod->tags + frame * mod->numTags;
+	tagName = mod->tagNames;
+	for(i = 0; i < mod->numTags; i++, tag++, tagName++)
+	{
+		if(!strcmp(tagName->name, _tagName))
+		{
+			return tag;
+		}
+	}
+
+	return NULL;
+}
+
+#ifdef RAVENMD4
+void R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest) 
+{
+	int				i, j, k;
+	int				frameSize;
+	mdrFrame_t		*frame;
+	mdrTag_t		*tag;
+
+	if ( framenum >= mod->numFrames ) 
+	{
+		// it is possible to have a bad frame while changing models, so don't error
+		framenum = mod->numFrames - 1;
+	}
+
+	tag = (mdrTag_t *)((byte *)mod + mod->ofsTags);
+	for ( i = 0 ; i < mod->numTags ; i++, tag++ )
+	{
+		if ( !strcmp( tag->name, tagName ) )
+		{
+			Q_strncpyz(dest->name, tag->name, sizeof(dest->name));
+
+			// uncompressed model...
+			//
+			frameSize = (intptr_t)( &((mdrFrame_t *)0)->bones[ mod->numBones ] );
+			frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize );
+
+			for (j = 0; j < 3; j++)
+			{
+				for (k = 0; k < 3; k++)
+					dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j];
+			}
+
+			dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3];
+			dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3];
+			dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3];				
+
+			return;
+		}
+	}
+
+	AxisClear( dest->axis );
+	VectorClear( dest->origin );
+	strcpy(dest->name,"");
+}
+#endif
+
+/*
+================
+R_LerpTag
+================
+*/
+int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, 
+					 float frac, const char *tagName ) {
+	mdvTag_t	*start, *end;
+#ifdef RAVENMD4
+	md3Tag_t	start_space, end_space;
+#endif
+	int		i;
+	float		frontLerp, backLerp;
+	model_t		*model;
+
+	model = R_GetModelByHandle( handle );
+	if ( !model->mdv[0] )
+	{
+#ifdef RAVENMD4
+		if(model->type == MOD_MDR)
+		{
+			start = &start_space;
+			end = &end_space;
+			R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, start);
+			R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, end);
+		}
+		else
+#endif
+		if( model->type == MOD_IQM ) {
+			return R_IQMLerpTag( tag, model->modelData,
+					startFrame, endFrame,
+					frac, tagName );
+		} else {
+
+			AxisClear( tag->axis );
+			VectorClear( tag->origin );
+			return qfalse;
+
+		}
+	}
+	else
+	{
+		start = R_GetTag( model->mdv[0], startFrame, tagName );
+		end = R_GetTag( model->mdv[0], endFrame, tagName );
+		if ( !start || !end ) {
+			AxisClear( tag->axis );
+			VectorClear( tag->origin );
+			return qfalse;
+		}
+	}
+	
+	frontLerp = frac;
+	backLerp = 1.0f - frac;
+
+	for ( i = 0 ; i < 3 ; i++ ) {
+		tag->origin[i] = start->origin[i] * backLerp +  end->origin[i] * frontLerp;
+		tag->axis[0][i] = start->axis[0][i] * backLerp +  end->axis[0][i] * frontLerp;
+		tag->axis[1][i] = start->axis[1][i] * backLerp +  end->axis[1][i] * frontLerp;
+		tag->axis[2][i] = start->axis[2][i] * backLerp +  end->axis[2][i] * frontLerp;
+	}
+	VectorNormalize( tag->axis[0] );
+	VectorNormalize( tag->axis[1] );
+	VectorNormalize( tag->axis[2] );
+	return qtrue;
+}
+
+
+/*
+====================
+R_ModelBounds
+====================
+*/
+void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) {
+	model_t		*model;
+
+	model = R_GetModelByHandle( handle );
+
+	if(model->type == MOD_BRUSH) {
+		VectorCopy( model->bmodel->bounds[0], mins );
+		VectorCopy( model->bmodel->bounds[1], maxs );
+		
+		return;
+	} else if (model->type == MOD_MESH) {
+		mdvModel_t	*header;
+		mdvFrame_t	*frame;
+
+		header = model->mdv[0];
+		frame = header->frames;
+
+		VectorCopy( frame->bounds[0], mins );
+		VectorCopy( frame->bounds[1], maxs );
+		
+		return;
+	} else if (model->type == MOD_MD4) {
+		md4Header_t	*header;
+		md4Frame_t	*frame;
+
+		header = (md4Header_t *)model->modelData;
+		frame = (md4Frame_t *) ((byte *)header + header->ofsFrames);
+
+		VectorCopy( frame->bounds[0], mins );
+		VectorCopy( frame->bounds[1], maxs );
+		
+		return;
+#ifdef RAVENMD4
+	} else if (model->type == MOD_MDR) {
+		mdrHeader_t	*header;
+		mdrFrame_t	*frame;
+
+		header = (mdrHeader_t *)model->modelData;
+		frame = (mdrFrame_t *) ((byte *)header + header->ofsFrames);
+
+		VectorCopy( frame->bounds[0], mins );
+		VectorCopy( frame->bounds[1], maxs );
+		
+		return;
+#endif
+	} else if(model->type == MOD_IQM) {
+		iqmData_t *iqmData;
+		
+		iqmData = model->modelData;
+
+		if(iqmData->bounds)
+		{
+			VectorCopy(iqmData->bounds, mins);
+			VectorCopy(iqmData->bounds + 3, maxs);
+			return;
+		}
+	}
+
+	VectorClear( mins );
+	VectorClear( maxs );
+}

Added: trunk/code/rend2/tr_model_iqm.c
===================================================================
--- trunk/code/rend2/tr_model_iqm.c	                        (rev 0)
+++ trunk/code/rend2/tr_model_iqm.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1058 @@
+/*
+===========================================================================
+Copyright (C) 2011 Thilo Schulz <thilo at tjps.eu>
+Copyright (C) 2011 Matthias Bentrup <matthias.bentrup at googlemail.com>
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+#define	LL(x) x=LittleLong(x)
+
+static qboolean IQM_CheckRange( iqmHeader_t *header, int offset,
+				int count,int size ) {
+	// return true if the range specified by offset, count and size
+	// doesn't fit into the file
+	return ( count <= 0 ||
+		 offset < 0 ||
+		 offset > header->filesize ||
+		 offset + count * size < 0 ||
+		 offset + count * size > header->filesize );
+}
+// "multiply" 3x4 matrices, these are assumed to be the top 3 rows
+// of a 4x4 matrix with the last row = (0 0 0 1)
+static void Matrix34Multiply( float *a, float *b, float *out ) {
+	out[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8];
+	out[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9];
+	out[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10];
+	out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3];
+	out[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8];
+	out[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9];
+	out[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10];
+	out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7];
+	out[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8];
+	out[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9];
+	out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10];
+	out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11];
+}
+static void InterpolateMatrix( float *a, float *b, float lerp, float *mat ) {
+	float unLerp = 1.0f - lerp;
+
+	mat[ 0] = a[ 0] * unLerp + b[ 0] * lerp;
+	mat[ 1] = a[ 1] * unLerp + b[ 1] * lerp;
+	mat[ 2] = a[ 2] * unLerp + b[ 2] * lerp;
+	mat[ 3] = a[ 3] * unLerp + b[ 3] * lerp;
+	mat[ 4] = a[ 4] * unLerp + b[ 4] * lerp;
+	mat[ 5] = a[ 5] * unLerp + b[ 5] * lerp;
+	mat[ 6] = a[ 6] * unLerp + b[ 6] * lerp;
+	mat[ 7] = a[ 7] * unLerp + b[ 7] * lerp;
+	mat[ 8] = a[ 8] * unLerp + b[ 8] * lerp;
+	mat[ 9] = a[ 9] * unLerp + b[ 9] * lerp;
+	mat[10] = a[10] * unLerp + b[10] * lerp;
+	mat[11] = a[11] * unLerp + b[11] * lerp;
+}
+static void JointToMatrix( vec4_t rot, vec3_t scale, vec3_t trans,
+			   float *mat ) {
+	float xx = 2.0f * rot[0] * rot[0];
+	float yy = 2.0f * rot[1] * rot[1];
+	float zz = 2.0f * rot[2] * rot[2];
+	float xy = 2.0f * rot[0] * rot[1];
+	float xz = 2.0f * rot[0] * rot[2];
+	float yz = 2.0f * rot[1] * rot[2];
+	float wx = 2.0f * rot[3] * rot[0];
+	float wy = 2.0f * rot[3] * rot[1];
+	float wz = 2.0f * rot[3] * rot[2];
+
+	mat[ 0] = scale[0] * (1.0f - (yy + zz));
+	mat[ 1] = scale[0] * (xy - wz);
+	mat[ 2] = scale[0] * (xz + wy);
+	mat[ 3] = trans[0];
+	mat[ 4] = scale[1] * (xy + wz);
+	mat[ 5] = scale[1] * (1.0f - (xx + zz));
+	mat[ 6] = scale[1] * (yz - wx);
+	mat[ 7] = trans[1];
+	mat[ 8] = scale[2] * (xz - wy);
+	mat[ 9] = scale[2] * (yz + wx);
+	mat[10] = scale[2] * (1.0f - (xx + yy));
+	mat[11] = trans[2];
+}
+static void Matrix34Invert( float *inMat, float *outMat )
+{
+	vec3_t trans;
+	float invSqrLen, *v;
+ 
+	outMat[ 0] = inMat[ 0]; outMat[ 1] = inMat[ 4]; outMat[ 2] = inMat[ 8];
+	outMat[ 4] = inMat[ 1]; outMat[ 5] = inMat[ 5]; outMat[ 6] = inMat[ 9];
+	outMat[ 8] = inMat[ 2]; outMat[ 9] = inMat[ 6]; outMat[10] = inMat[10];
+
+	v = outMat + 0; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+	v = outMat + 4; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+	v = outMat + 8; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v);
+
+	trans[0] = inMat[ 3];
+	trans[1] = inMat[ 7];
+	trans[2] = inMat[11];
+
+	outMat[ 3] = -DotProduct(outMat + 0, trans);
+	outMat[ 7] = -DotProduct(outMat + 4, trans);
+	outMat[11] = -DotProduct(outMat + 8, trans);
+}
+
+/*
+=================
+R_LoadIQM
+
+Load an IQM model and compute the joint matrices for every frame.
+=================
+*/
+qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_name ) {
+	iqmHeader_t		*header;
+	iqmVertexArray_t	*vertexarray;
+	iqmTriangle_t		*triangle;
+	iqmMesh_t		*mesh;
+	iqmJoint_t		*joint;
+	iqmPose_t		*pose;
+	iqmBounds_t		*bounds;
+	unsigned short		*framedata;
+	char			*str;
+	int			i, j;
+	float			jointMats[IQM_MAX_JOINTS * 2 * 12];
+	float			*mat;
+	size_t			size, joint_names;
+	iqmData_t		*iqmData;
+	srfIQModel_t		*surface;
+
+	if( filesize < sizeof(iqmHeader_t) ) {
+		return qfalse;
+	}
+
+	header = (iqmHeader_t *)buffer;
+	if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) {
+		return qfalse;
+	}
+
+	LL( header->version );
+	if( header->version != IQM_VERSION ) {
+		ri.Printf(PRINT_WARNING, "R_LoadIQM: %s is a unsupported IQM version (%d), only version %d is supported.\n",
+				mod_name, header->version, IQM_VERSION);
+		return qfalse;
+	}
+
+	LL( header->filesize );
+	if( header->filesize > filesize || header->filesize > 16<<20 ) {
+		return qfalse;
+	}
+
+	LL( header->flags );
+	LL( header->num_text );
+	LL( header->ofs_text );
+	LL( header->num_meshes );
+	LL( header->ofs_meshes );
+	LL( header->num_vertexarrays );
+	LL( header->num_vertexes );
+	LL( header->ofs_vertexarrays );
+	LL( header->num_triangles );
+	LL( header->ofs_triangles );
+	LL( header->ofs_adjacency );
+	LL( header->num_joints );
+	LL( header->ofs_joints );
+	LL( header->num_poses );
+	LL( header->ofs_poses );
+	LL( header->num_anims );
+	LL( header->ofs_anims );
+	LL( header->num_frames );
+	LL( header->num_framechannels );
+	LL( header->ofs_frames );
+	LL( header->ofs_bounds );
+	LL( header->num_comment );
+	LL( header->ofs_comment );
+	LL( header->num_extensions );
+	LL( header->ofs_extensions );
+
+	// check ioq3 joint limit
+	if ( header->num_joints > IQM_MAX_JOINTS ) {
+		ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %d joints (%d).\n",
+				mod_name, IQM_MAX_JOINTS, header->num_joints);
+		return qfalse;
+	}
+
+	// check and swap vertex arrays
+	if( IQM_CheckRange( header, header->ofs_vertexarrays,
+			    header->num_vertexarrays,
+			    sizeof(iqmVertexArray_t) ) ) {
+		return qfalse;
+	}
+	vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
+	for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
+		int	j, n, *intPtr;
+
+		if( vertexarray->size <= 0 || vertexarray->size > 4 ) {
+			return qfalse;
+		}
+
+		// total number of values
+		n = header->num_vertexes * vertexarray->size;
+
+		switch( vertexarray->format ) {
+		case IQM_BYTE:
+		case IQM_UBYTE:
+			// 1 byte, no swapping necessary
+			if( IQM_CheckRange( header, vertexarray->offset,
+					    n, sizeof(byte) ) ) {
+				return qfalse;
+			}
+			break;
+		case IQM_INT:
+		case IQM_UINT:
+		case IQM_FLOAT:
+			// 4-byte swap
+			if( IQM_CheckRange( header, vertexarray->offset,
+					    n, sizeof(float) ) ) {
+				return qfalse;
+			}
+			intPtr = (int *)((byte *)header + vertexarray->offset);
+			for( j = 0; j < n; j++, intPtr++ ) {
+				LL( *intPtr );
+			}
+			break;
+		default:
+			// not supported
+			return qfalse;
+			break;
+		}
+
+		switch( vertexarray->type ) {
+		case IQM_POSITION:
+		case IQM_NORMAL:
+			if( vertexarray->format != IQM_FLOAT ||
+			    vertexarray->size != 3 ) {
+				return qfalse;
+			}
+			break;
+		case IQM_TANGENT:
+			if( vertexarray->format != IQM_FLOAT ||
+			    vertexarray->size != 4 ) {
+				return qfalse;
+			}
+			break;
+		case IQM_TEXCOORD:
+			if( vertexarray->format != IQM_FLOAT ||
+			    vertexarray->size != 2 ) {
+				return qfalse;
+			}
+			break;
+		case IQM_BLENDINDEXES:
+		case IQM_BLENDWEIGHTS:
+			if( vertexarray->format != IQM_UBYTE ||
+			    vertexarray->size != 4 ) {
+				return qfalse;
+			}
+			break;
+		case IQM_COLOR:
+			if( vertexarray->format != IQM_UBYTE ||
+			    vertexarray->size != 4 ) {
+				return qfalse;
+			}
+			break;
+		}
+	}
+
+	// check and swap triangles
+	if( IQM_CheckRange( header, header->ofs_triangles,
+			    header->num_triangles, sizeof(iqmTriangle_t) ) ) {
+		return qfalse;
+	}
+	triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
+	for( i = 0; i < header->num_triangles; i++, triangle++ ) {
+		LL( triangle->vertex[0] );
+		LL( triangle->vertex[1] );
+		LL( triangle->vertex[2] );
+		
+		if( triangle->vertex[0] > header->num_vertexes ||
+		    triangle->vertex[1] > header->num_vertexes ||
+		    triangle->vertex[2] > header->num_vertexes ) {
+			return qfalse;
+		}
+	}
+
+	// check and swap meshes
+	if( IQM_CheckRange( header, header->ofs_meshes,
+			    header->num_meshes, sizeof(iqmMesh_t) ) ) {
+		return qfalse;
+	}
+	mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
+	for( i = 0; i < header->num_meshes; i++, mesh++) {
+		LL( mesh->name );
+		LL( mesh->material );
+		LL( mesh->first_vertex );
+		LL( mesh->num_vertexes );
+		LL( mesh->first_triangle );
+		LL( mesh->num_triangles );
+
+		// check ioq3 limits
+		if ( mesh->num_vertexes > SHADER_MAX_VERTEXES ) 
+		{
+			ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on a surface (%i).\n",
+				  mod_name, SHADER_MAX_VERTEXES, mesh->num_vertexes );
+			return qfalse;
+		}
+		if ( mesh->num_triangles*3 > SHADER_MAX_INDEXES ) 
+		{
+			ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on a surface (%i).\n",
+				  mod_name, SHADER_MAX_INDEXES / 3, mesh->num_triangles );
+			return qfalse;
+		}
+
+		if( mesh->first_vertex >= header->num_vertexes ||
+		    mesh->first_vertex + mesh->num_vertexes > header->num_vertexes ||
+		    mesh->first_triangle >= header->num_triangles ||
+		    mesh->first_triangle + mesh->num_triangles > header->num_triangles ||
+		    mesh->name >= header->num_text ||
+		    mesh->material >= header->num_text ) {
+			return qfalse;
+		}
+	}
+
+	// check and swap joints
+	if( IQM_CheckRange( header, header->ofs_joints,
+			    header->num_joints, sizeof(iqmJoint_t) ) ) {
+		return qfalse;
+	}
+	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
+	joint_names = 0;
+	for( i = 0; i < header->num_joints; i++, joint++ ) {
+		LL( joint->name );
+		LL( joint->parent );
+		LL( joint->translate[0] );
+		LL( joint->translate[1] );
+		LL( joint->translate[2] );
+		LL( joint->rotate[0] );
+		LL( joint->rotate[1] );
+		LL( joint->rotate[2] );
+		LL( joint->rotate[3] );
+		LL( joint->scale[0] );
+		LL( joint->scale[1] );
+		LL( joint->scale[2] );
+
+		if( joint->parent < -1 ||
+		    joint->parent >= (int)header->num_joints ||
+		    joint->name >= (int)header->num_text ) {
+			return qfalse;
+		}
+		joint_names += strlen( (char *)header + header->ofs_text +
+				       joint->name ) + 1;
+	}
+
+	// check and swap poses
+	if( header->num_poses != header->num_joints ) {
+		return qfalse;
+	}
+	if( IQM_CheckRange( header, header->ofs_poses,
+			    header->num_poses, sizeof(iqmPose_t) ) ) {
+		return qfalse;
+	}
+	pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
+	for( i = 0; i < header->num_poses; i++, pose++ ) {
+		LL( pose->parent );
+		LL( pose->mask );
+		LL( pose->channeloffset[0] );
+		LL( pose->channeloffset[1] );
+		LL( pose->channeloffset[2] );
+		LL( pose->channeloffset[3] );
+		LL( pose->channeloffset[4] );
+		LL( pose->channeloffset[5] );
+		LL( pose->channeloffset[6] );
+		LL( pose->channeloffset[7] );
+		LL( pose->channeloffset[8] );
+		LL( pose->channeloffset[9] );
+		LL( pose->channelscale[0] );
+		LL( pose->channelscale[1] );
+		LL( pose->channelscale[2] );
+		LL( pose->channelscale[3] );
+		LL( pose->channelscale[4] );
+		LL( pose->channelscale[5] );
+		LL( pose->channelscale[6] );
+		LL( pose->channelscale[7] );
+		LL( pose->channelscale[8] );
+		LL( pose->channelscale[9] );
+	}
+
+	if (header->ofs_bounds)
+	{
+		// check and swap model bounds
+		if(IQM_CheckRange(header, header->ofs_bounds,
+				  header->num_frames, sizeof(*bounds)))
+		{
+			return qfalse;
+		}
+		bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds);
+		for(i = 0; i < header->num_frames; i++)
+		{
+			LL(bounds->bbmin[0]);
+			LL(bounds->bbmin[1]);
+			LL(bounds->bbmin[2]);
+			LL(bounds->bbmax[0]);
+			LL(bounds->bbmax[1]);
+			LL(bounds->bbmax[2]);
+
+			bounds++;
+		}
+	}
+
+	// allocate the model and copy the data
+	size = sizeof(iqmData_t);
+	size += header->num_meshes * sizeof( srfIQModel_t );
+	size += header->num_joints * header->num_frames * 12 * sizeof( float );
+	if(header->ofs_bounds)
+		size += header->num_frames * 6 * sizeof(float);	// model bounds
+	size += header->num_vertexes * 3 * sizeof(float);	// positions
+	size += header->num_vertexes * 2 * sizeof(float);	// texcoords
+	size += header->num_vertexes * 3 * sizeof(float);	// normals
+	size += header->num_vertexes * 4 * sizeof(float);	// tangents
+	size += header->num_vertexes * 4 * sizeof(byte);	// blendIndexes
+	size += header->num_vertexes * 4 * sizeof(byte);	// blendWeights
+	size += header->num_vertexes * 4 * sizeof(byte);	// colors
+	size += header->num_joints * sizeof(int);		// parents
+	size += header->num_triangles * 3 * sizeof(int);	// triangles
+	size += joint_names;					// joint names
+
+	mod->type = MOD_IQM;
+	iqmData = (iqmData_t *)ri.Hunk_Alloc( size, h_low );
+	mod->modelData = iqmData;
+
+	// fill header
+	iqmData->num_vertexes = header->num_vertexes;
+	iqmData->num_triangles = header->num_triangles;
+	iqmData->num_frames   = header->num_frames;
+	iqmData->num_surfaces = header->num_meshes;
+	iqmData->num_joints   = header->num_joints;
+	iqmData->surfaces     = (srfIQModel_t *)(iqmData + 1);
+	iqmData->poseMats     = (float *) (iqmData->surfaces + iqmData->num_surfaces);
+	if(header->ofs_bounds)
+	{
+		iqmData->bounds       = iqmData->poseMats + 12 * header->num_joints * header->num_frames;
+		iqmData->positions    = iqmData->bounds + 6 * header->num_frames;
+	}
+	else
+		iqmData->positions    = iqmData->poseMats + 12 * header->num_joints * header->num_frames;
+	iqmData->texcoords    = iqmData->positions + 3 * header->num_vertexes;
+	iqmData->normals      = iqmData->texcoords + 2 * header->num_vertexes;
+	iqmData->tangents     = iqmData->normals + 3 * header->num_vertexes;
+	iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes);
+	iqmData->blendWeights = iqmData->blendIndexes + 4 * header->num_vertexes;
+	iqmData->colors       = iqmData->blendWeights + 4 * header->num_vertexes;
+	iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes);
+	iqmData->triangles    = iqmData->jointParents + header->num_joints;
+	iqmData->names        = (char *)(iqmData->triangles + 3 * header->num_triangles);
+
+	// calculate joint matrices and their inverses
+	// they are needed only until the pose matrices are calculated
+	mat = jointMats;
+	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
+	for( i = 0; i < header->num_joints; i++, joint++ ) {
+		float baseFrame[12], invBaseFrame[12];
+ 
+		JointToMatrix( joint->rotate, joint->scale, joint->translate, baseFrame );
+		Matrix34Invert( baseFrame, invBaseFrame );
+ 
+		if ( joint->parent >= 0 )
+		{
+			Matrix34Multiply( jointMats + 2 * 12 * joint->parent, baseFrame, mat );
+			mat += 12;
+			Matrix34Multiply( invBaseFrame, jointMats + 2 * 12 * joint->parent + 12, mat );
+			mat += 12;
+ 		}
+		else
+		{
+			Com_Memcpy( mat, baseFrame,    sizeof(baseFrame)    );
+			mat += 12;
+			Com_Memcpy( mat, invBaseFrame, sizeof(invBaseFrame) );
+			mat += 12;
+ 		}
+	}
+
+	// calculate pose matrices
+	framedata = (unsigned short *)((byte *)header + header->ofs_frames);
+	mat = iqmData->poseMats;
+	for( i = 0; i < header->num_frames; i++ ) {
+		pose = (iqmPose_t *)((byte *)header + header->ofs_poses);
+		for( j = 0; j < header->num_poses; j++, pose++ ) {
+			vec3_t	translate;
+			vec4_t	rotate;
+			vec3_t	scale;
+			float	mat1[12], mat2[12];
+
+			translate[0] = pose->channeloffset[0];
+			if( pose->mask & 0x001)
+				translate[0] += *framedata++ * pose->channelscale[0];
+			translate[1] = pose->channeloffset[1];
+			if( pose->mask & 0x002)
+				translate[1] += *framedata++ * pose->channelscale[1];
+			translate[2] = pose->channeloffset[2];
+			if( pose->mask & 0x004)
+				translate[2] += *framedata++ * pose->channelscale[2];
+
+			rotate[0] = pose->channeloffset[3];
+			if( pose->mask & 0x008)
+				rotate[0] += *framedata++ * pose->channelscale[3];
+			rotate[1] = pose->channeloffset[4];
+			if( pose->mask & 0x010)
+				rotate[1] += *framedata++ * pose->channelscale[4];
+			rotate[2] = pose->channeloffset[5];
+			if( pose->mask & 0x020)
+				rotate[2] += *framedata++ * pose->channelscale[5];
+			rotate[3] = pose->channeloffset[6];
+			if( pose->mask & 0x040)
+				rotate[3] += *framedata++ * pose->channelscale[6];
+
+			scale[0] = pose->channeloffset[7];
+			if( pose->mask & 0x080)
+				scale[0] += *framedata++ * pose->channelscale[7];
+			scale[1] = pose->channeloffset[8];
+			if( pose->mask & 0x100)
+				scale[1] += *framedata++ * pose->channelscale[8];
+			scale[2] = pose->channeloffset[9];
+			if( pose->mask & 0x200)
+				scale[2] += *framedata++ * pose->channelscale[9];
+
+			// construct transformation matrix
+			JointToMatrix( rotate, scale, translate, mat1 );
+			
+			if( pose->parent >= 0 ) {
+				Matrix34Multiply( jointMats + 12 * 2 * pose->parent,
+						  mat1, mat2 );
+			} else {
+				Com_Memcpy( mat2, mat1, sizeof(mat1) );
+			}
+			
+			Matrix34Multiply( mat2, jointMats + 12 * (2 * j + 1), mat );
+			mat += 12;
+		}
+	}
+
+	// register shaders
+	// overwrite the material offset with the shader index
+	mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes);
+	surface = iqmData->surfaces;
+	str = (char *)header + header->ofs_text;
+	for( i = 0; i < header->num_meshes; i++, mesh++, surface++ ) {
+		surface->surfaceType = SF_IQM;
+		Q_strncpyz(surface->name, str + mesh->name, sizeof (surface->name));
+		Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster
+		surface->shader = R_FindShader( str + mesh->material, LIGHTMAP_NONE, qtrue );
+		if( surface->shader->defaultShader )
+			surface->shader = tr.defaultShader;
+		surface->data = iqmData;
+		surface->first_vertex = mesh->first_vertex;
+		surface->num_vertexes = mesh->num_vertexes;
+		surface->first_triangle = mesh->first_triangle;
+		surface->num_triangles = mesh->num_triangles;
+	}
+
+	// copy vertexarrays and indexes
+	vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
+	for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
+		int	n;
+
+		// total number of values
+		n = header->num_vertexes * vertexarray->size;
+
+		switch( vertexarray->type ) {
+		case IQM_POSITION:
+			Com_Memcpy( iqmData->positions,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(float) );
+			break;
+		case IQM_NORMAL:
+			Com_Memcpy( iqmData->normals,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(float) );
+			break;
+		case IQM_TANGENT:
+			Com_Memcpy( iqmData->tangents,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(float) );
+			break;
+		case IQM_TEXCOORD:
+			Com_Memcpy( iqmData->texcoords,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(float) );
+			break;
+		case IQM_BLENDINDEXES:
+			Com_Memcpy( iqmData->blendIndexes,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(byte) );
+			break;
+		case IQM_BLENDWEIGHTS:
+			Com_Memcpy( iqmData->blendWeights,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(byte) );
+			break;
+		case IQM_COLOR:
+			Com_Memcpy( iqmData->colors,
+				    (byte *)header + vertexarray->offset,
+				    n * sizeof(byte) );
+			break;
+		}
+	}
+
+	// copy joint parents
+	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
+	for( i = 0; i < header->num_joints; i++, joint++ ) {
+		iqmData->jointParents[i] = joint->parent;
+	}
+
+	// copy triangles
+	triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles);
+	for( i = 0; i < header->num_triangles; i++, triangle++ ) {
+		iqmData->triangles[3*i+0] = triangle->vertex[0];
+		iqmData->triangles[3*i+1] = triangle->vertex[1];
+		iqmData->triangles[3*i+2] = triangle->vertex[2];
+	}
+
+	// copy joint names
+	str = iqmData->names;
+	joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
+	for( i = 0; i < header->num_joints; i++, joint++ ) {
+		char *name = (char *)header + header->ofs_text +
+			joint->name;
+		int len = strlen( name ) + 1;
+		Com_Memcpy( str, name, len );
+		str += len;
+	}
+
+	// copy model bounds
+	if(header->ofs_bounds)
+	{
+		mat = iqmData->bounds;
+		bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds);
+		for(i = 0; i < header->num_frames; i++)
+		{
+			mat[0] = bounds->bbmin[0];
+			mat[1] = bounds->bbmin[1];
+			mat[2] = bounds->bbmin[2];
+			mat[3] = bounds->bbmax[0];
+			mat[4] = bounds->bbmax[1];
+			mat[5] = bounds->bbmax[2];
+
+			mat += 6;
+			bounds++;
+		}
+	}
+
+	return qtrue;
+}
+
+/*
+=============
+R_CullIQM
+=============
+*/
+static int R_CullIQM( iqmData_t *data, trRefEntity_t *ent ) {
+	vec3_t		bounds[2];
+	vec_t		*oldBounds, *newBounds;
+	int		i;
+
+	if (!data->bounds) {
+		tr.pc.c_box_cull_md3_clip++;
+		return CULL_CLIP;
+	}
+
+	// compute bounds pointers
+	oldBounds = data->bounds + 6*ent->e.oldframe;
+	newBounds = data->bounds + 6*ent->e.frame;
+
+	// calculate a bounding box in the current coordinate system
+	for (i = 0 ; i < 3 ; i++) {
+		bounds[0][i] = oldBounds[i] < newBounds[i] ? oldBounds[i] : newBounds[i];
+		bounds[1][i] = oldBounds[i+3] > newBounds[i+3] ? oldBounds[i+3] : newBounds[i+3];
+	}
+
+	switch ( R_CullLocalBox( bounds ) )
+	{
+	case CULL_IN:
+		tr.pc.c_box_cull_md3_in++;
+		return CULL_IN;
+	case CULL_CLIP:
+		tr.pc.c_box_cull_md3_clip++;
+		return CULL_CLIP;
+	case CULL_OUT:
+	default:
+		tr.pc.c_box_cull_md3_out++;
+		return CULL_OUT;
+	}
+}
+
+/*
+=================
+R_ComputeIQMFogNum
+
+=================
+*/
+int R_ComputeIQMFogNum( iqmData_t *data, trRefEntity_t *ent ) {
+	int			i, j;
+	fog_t			*fog;
+	const vec_t		*bounds;
+	const vec_t		defaultBounds[6] = { -8, -8, -8, 8, 8, 8 };
+	vec3_t			diag, center;
+	vec3_t			localOrigin;
+	vec_t			radius;
+
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		return 0;
+	}
+
+	// FIXME: non-normalized axis issues
+	if (data->bounds) {
+		bounds = data->bounds + 6*ent->e.frame;
+	} else {
+		bounds = defaultBounds;
+	}
+	VectorSubtract( bounds+3, bounds, diag );
+	VectorMA( bounds, 0.5f, diag, center );
+	VectorAdd( ent->e.origin, center, localOrigin );
+	radius = 0.5f * VectorLength( diag );
+
+	for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+		fog = &tr.world->fogs[i];
+		for ( j = 0 ; j < 3 ; j++ ) {
+			if ( localOrigin[j] - radius >= fog->bounds[1][j] ) {
+				break;
+			}
+			if ( localOrigin[j] + radius <= fog->bounds[0][j] ) {
+				break;
+			}
+		}
+		if ( j == 3 ) {
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+/*
+=================
+R_AddIQMSurfaces
+
+Add all surfaces of this model
+=================
+*/
+void R_AddIQMSurfaces( trRefEntity_t *ent ) {
+	iqmData_t		*data;
+	srfIQModel_t		*surface;
+	int			i, j;
+	qboolean		personalModel;
+	int			cull;
+	int			fogNum;
+	shader_t		*shader;
+	skin_t			*skin;
+
+	data = tr.currentModel->modelData;
+	surface = data->surfaces;
+
+	// don't add third_person objects if not in a portal
+	personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal;
+
+	if ( ent->e.renderfx & RF_WRAP_FRAMES ) {
+		ent->e.frame %= data->num_frames;
+		ent->e.oldframe %= data->num_frames;
+	}
+
+	//
+	// Validate the frames so there is no chance of a crash.
+	// This will write directly into the entity structure, so
+	// when the surfaces are rendered, they don't need to be
+	// range checked again.
+	//
+	if ( (ent->e.frame >= data->num_frames) 
+	     || (ent->e.frame < 0)
+	     || (ent->e.oldframe >= data->num_frames)
+	     || (ent->e.oldframe < 0) ) {
+		ri.Printf( PRINT_DEVELOPER, "R_AddIQMSurfaces: no such frame %d to %d for '%s'\n",
+			   ent->e.oldframe, ent->e.frame,
+			   tr.currentModel->name );
+		ent->e.frame = 0;
+		ent->e.oldframe = 0;
+	}
+
+	//
+	// cull the entire model if merged bounding box of both frames
+	// is outside the view frustum.
+	//
+	cull = R_CullIQM ( data, ent );
+	if ( cull == CULL_OUT ) {
+		return;
+	}
+
+	//
+	// set up lighting now that we know we aren't culled
+	//
+	if ( !personalModel || r_shadows->integer > 1 ) {
+		R_SetupEntityLighting( &tr.refdef, ent );
+	}
+
+	//
+	// see if we are in a fog volume
+	//
+	fogNum = R_ComputeIQMFogNum( data, ent );
+
+	for ( i = 0 ; i < data->num_surfaces ; i++ ) {
+		if(ent->e.customShader)
+			shader = R_GetShaderByHandle( ent->e.customShader );
+		else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins)
+		{
+			skin = R_GetSkinByHandle(ent->e.customSkin);
+			shader = tr.defaultShader;
+
+			for(j = 0; j < skin->numSurfaces; j++)
+			{
+				if (!strcmp(skin->surfaces[j]->name, surface->name))
+				{
+					shader = skin->surfaces[j]->shader;
+					break;
+				}
+			}
+		} else {
+			shader = surface->shader;
+		}
+
+		// we will add shadows even if the main object isn't visible in the view
+
+		// stencil shadows can't do personal models unless I polyhedron clip
+		if ( !personalModel
+			&& r_shadows->integer == 2 
+			&& fogNum == 0
+			&& !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) 
+			&& shader->sort == SS_OPAQUE ) {
+			R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, 0, 0 );
+		}
+
+		// projection shadows work fine with personal models
+		if ( r_shadows->integer == 3
+			&& fogNum == 0
+			&& (ent->e.renderfx & RF_SHADOW_PLANE )
+			&& shader->sort == SS_OPAQUE ) {
+			R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, 0, 0 );
+		}
+
+		if( !personalModel ) {
+			R_AddDrawSurf( (void *)surface, shader, fogNum, 0, 0 );
+		}
+
+		surface++;
+	}
+}
+
+
+static void ComputeJointMats( iqmData_t *data, int frame, int oldframe,
+			      float backlerp, float *mat ) {
+	float	*mat1, *mat2;
+	int	*joint = data->jointParents;
+	int	i;
+
+	if ( oldframe == frame ) {
+		mat1 = data->poseMats + 12 * data->num_joints * frame;
+		for( i = 0; i < data->num_joints; i++, joint++ ) {
+			if( *joint >= 0 ) {
+				Matrix34Multiply( mat + 12 * *joint,
+						  mat1 + 12*i, mat + 12*i );
+			} else {
+				Com_Memcpy( mat + 12*i, mat1 + 12*i, 12 * sizeof(float) );
+			}
+		}
+	} else  {
+		mat1 = data->poseMats + 12 * data->num_joints * frame;
+		mat2 = data->poseMats + 12 * data->num_joints * oldframe;
+		
+		for( i = 0; i < data->num_joints; i++, joint++ ) {
+			if( *joint >= 0 ) {
+				float tmpMat[12];
+				InterpolateMatrix( mat1 + 12*i, mat2 + 12*i,
+						   backlerp, tmpMat );
+				Matrix34Multiply( mat + 12 * *joint,
+						  tmpMat, mat + 12*i );
+				
+			} else {
+				InterpolateMatrix( mat1 + 12*i, mat2 + 12*i,
+						   backlerp, mat );
+			}
+		}
+	}
+}
+
+
+/*
+=================
+RB_AddIQMSurfaces
+
+Compute vertices for this model surface
+=================
+*/
+void RB_IQMSurfaceAnim( surfaceType_t *surface ) {
+	srfIQModel_t	*surf = (srfIQModel_t *)surface;
+	iqmData_t	*data = surf->data;
+	float		jointMats[IQM_MAX_JOINTS * 12];
+	int		i;
+
+	vec4_t		*outXYZ = &tess.xyz[tess.numVertexes];
+	vec4_t		*outNormal = &tess.normal[tess.numVertexes];
+	vec2_t		(*outTexCoord)[2] = &tess.texCoords[tess.numVertexes];
+	vec4_t	*outColor = &tess.vertexColors[tess.numVertexes];
+
+	int	frame = backEnd.currentEntity->e.frame % data->num_frames;
+	int	oldframe = backEnd.currentEntity->e.oldframe % data->num_frames;
+	float	backlerp = backEnd.currentEntity->e.backlerp;
+
+	int		*tri;
+	glIndex_t	*ptr;
+	glIndex_t	base;
+
+	RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 );
+
+	// compute interpolated joint matrices
+	ComputeJointMats( data, frame, oldframe, backlerp, jointMats );
+
+	// transform vertexes and fill other data
+	for( i = 0; i < surf->num_vertexes;
+	     i++, outXYZ++, outNormal++, outTexCoord++, outColor++ ) {
+		int	j, k;
+		float	vtxMat[12];
+		float	nrmMat[9];
+		int	vtx = i + surf->first_vertex;
+
+		// compute the vertex matrix by blending the up to
+		// four blend weights
+		for( k = 0; k < 12; k++ )
+			vtxMat[k] = data->blendWeights[4*vtx]
+				* jointMats[12*data->blendIndexes[4*vtx] + k];
+		for( j = 1; j < 4; j++ ) {
+			if( data->blendWeights[4*vtx + j] <= 0 )
+				break;
+			for( k = 0; k < 12; k++ )
+				vtxMat[k] += data->blendWeights[4*vtx + j]
+					* jointMats[12*data->blendIndexes[4*vtx + j] + k];
+		}
+		for( k = 0; k < 12; k++ )
+			vtxMat[k] *= 1.0f / 255.0f;
+
+		// compute the normal matrix as transpose of the adjoint
+		// of the vertex matrix
+		nrmMat[ 0] = vtxMat[ 5]*vtxMat[10] - vtxMat[ 6]*vtxMat[ 9];
+		nrmMat[ 1] = vtxMat[ 6]*vtxMat[ 8] - vtxMat[ 4]*vtxMat[10];
+		nrmMat[ 2] = vtxMat[ 4]*vtxMat[ 9] - vtxMat[ 5]*vtxMat[ 8];
+		nrmMat[ 3] = vtxMat[ 2]*vtxMat[ 9] - vtxMat[ 1]*vtxMat[10];
+		nrmMat[ 4] = vtxMat[ 0]*vtxMat[10] - vtxMat[ 2]*vtxMat[ 8];
+		nrmMat[ 5] = vtxMat[ 1]*vtxMat[ 8] - vtxMat[ 0]*vtxMat[ 9];
+		nrmMat[ 6] = vtxMat[ 1]*vtxMat[ 6] - vtxMat[ 2]*vtxMat[ 5];
+		nrmMat[ 7] = vtxMat[ 2]*vtxMat[ 4] - vtxMat[ 0]*vtxMat[ 6];
+		nrmMat[ 8] = vtxMat[ 0]*vtxMat[ 5] - vtxMat[ 1]*vtxMat[ 4];
+
+		(*outTexCoord)[0][0] = data->texcoords[2*vtx + 0];
+		(*outTexCoord)[0][1] = data->texcoords[2*vtx + 1];
+		(*outTexCoord)[1][0] = (*outTexCoord)[0][0];
+		(*outTexCoord)[1][1] = (*outTexCoord)[0][1];
+
+		(*outXYZ)[0] =
+			vtxMat[ 0] * data->positions[3*vtx+0] +
+			vtxMat[ 1] * data->positions[3*vtx+1] +
+			vtxMat[ 2] * data->positions[3*vtx+2] +
+			vtxMat[ 3];
+		(*outXYZ)[1] =
+			vtxMat[ 4] * data->positions[3*vtx+0] +
+			vtxMat[ 5] * data->positions[3*vtx+1] +
+			vtxMat[ 6] * data->positions[3*vtx+2] +
+			vtxMat[ 7];
+		(*outXYZ)[2] =
+			vtxMat[ 8] * data->positions[3*vtx+0] +
+			vtxMat[ 9] * data->positions[3*vtx+1] +
+			vtxMat[10] * data->positions[3*vtx+2] +
+			vtxMat[11];
+		(*outXYZ)[3] = 1.0f;
+
+		(*outNormal)[0] =
+			nrmMat[ 0] * data->normals[3*vtx+0] +
+			nrmMat[ 1] * data->normals[3*vtx+1] +
+			nrmMat[ 2] * data->normals[3*vtx+2];
+		(*outNormal)[1] =
+			nrmMat[ 3] * data->normals[3*vtx+0] +
+			nrmMat[ 4] * data->normals[3*vtx+1] +
+			nrmMat[ 5] * data->normals[3*vtx+2];
+		(*outNormal)[2] =
+			nrmMat[ 6] * data->normals[3*vtx+0] +
+			nrmMat[ 7] * data->normals[3*vtx+1] +
+			nrmMat[ 8] * data->normals[3*vtx+2];
+		(*outNormal)[3] = 0.0f;
+
+		(*outColor)[0] = data->colors[4*vtx+0] / 255.0f;
+		(*outColor)[1] = data->colors[4*vtx+1] / 255.0f;
+		(*outColor)[2] = data->colors[4*vtx+2] / 255.0f;
+		(*outColor)[3] = data->colors[4*vtx+3] / 255.0f;
+	}
+
+	tri = data->triangles + 3 * surf->first_triangle;
+	ptr = &tess.indexes[tess.numIndexes];
+	base = tess.numVertexes;
+
+	for( i = 0; i < surf->num_triangles; i++ ) {
+		*ptr++ = base + (*tri++ - surf->first_vertex);
+		*ptr++ = base + (*tri++ - surf->first_vertex);
+		*ptr++ = base + (*tri++ - surf->first_vertex);
+	}
+
+	tess.numIndexes += 3 * surf->num_triangles;
+	tess.numVertexes += surf->num_vertexes;
+}
+
+int R_IQMLerpTag( orientation_t *tag, iqmData_t *data,
+		  int startFrame, int endFrame, 
+		  float frac, const char *tagName ) {
+	float	jointMats[IQM_MAX_JOINTS * 12];
+	int	joint;
+	char	*names = data->names;
+
+	// get joint number by reading the joint names
+	for( joint = 0; joint < data->num_joints; joint++ ) {
+		if( !strcmp( tagName, names ) )
+			break;
+		names += strlen( names ) + 1;
+	}
+	if( joint >= data->num_joints ) {
+		AxisClear( tag->axis );
+		VectorClear( tag->origin );
+		return qfalse;
+	}
+
+	ComputeJointMats( data, startFrame, endFrame, frac, jointMats );
+
+	tag->axis[0][0] = jointMats[12 * joint + 0];
+	tag->axis[1][0] = jointMats[12 * joint + 1];
+	tag->axis[2][0] = jointMats[12 * joint + 2];
+	tag->origin[0] = jointMats[12 * joint + 3];
+	tag->axis[0][1] = jointMats[12 * joint + 4];
+	tag->axis[1][1] = jointMats[12 * joint + 5];
+	tag->axis[2][1] = jointMats[12 * joint + 6];
+	tag->origin[1] = jointMats[12 * joint + 7];
+	tag->axis[0][2] = jointMats[12 * joint + 8];
+	tag->axis[1][2] = jointMats[12 * joint + 9];
+	tag->axis[2][2] = jointMats[12 * joint + 10];
+	tag->origin[2] = jointMats[12 * joint + 11];
+
+	return qtrue;
+}

Added: trunk/code/rend2/tr_noise.c
===================================================================
--- trunk/code/rend2/tr_noise.c	                        (rev 0)
+++ trunk/code/rend2/tr_noise.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,93 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_noise.c
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+
+#define NOISE_SIZE 256
+#define NOISE_MASK ( NOISE_SIZE - 1 )
+
+#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )]
+#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) )
+
+static float s_noise_table[NOISE_SIZE];
+static int s_noise_perm[NOISE_SIZE];
+
+static float GetNoiseValue( int x, int y, int z, int t )
+{
+	int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t );
+
+	return s_noise_table[index];
+}
+
+void R_NoiseInit( void )
+{
+	int i;
+
+	for ( i = 0; i < NOISE_SIZE; i++ )
+	{
+		s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) );
+		s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 );
+	}
+}
+
+float R_NoiseGet4f( float x, float y, float z, float t )
+{
+	int i;
+	int ix, iy, iz, it;
+	float fx, fy, fz, ft;
+	float front[4];
+	float back[4];
+	float fvalue, bvalue, value[2], finalvalue;
+
+	ix = ( int ) floor( x );
+	fx = x - ix;
+	iy = ( int ) floor( y );
+	fy = y - iy;
+	iz = ( int ) floor( z );
+	fz = z - iz;
+	it = ( int ) floor( t );
+	ft = t - it;
+
+	for ( i = 0; i < 2; i++ )
+	{
+		front[0] = GetNoiseValue( ix, iy, iz, it + i );
+		front[1] = GetNoiseValue( ix+1, iy, iz, it + i );
+		front[2] = GetNoiseValue( ix, iy+1, iz, it + i );
+		front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i );
+
+		back[0] = GetNoiseValue( ix, iy, iz + 1, it + i );
+		back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i );
+		back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i );
+		back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i );
+
+		fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy );
+		bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy );
+
+		value[i] = LERP( fvalue, bvalue, fz );
+	}
+
+	finalvalue = LERP( value[0], value[1], ft );
+
+	return finalvalue;
+}

Added: trunk/code/rend2/tr_postprocess.c
===================================================================
--- trunk/code/rend2/tr_postprocess.c	                        (rev 0)
+++ trunk/code/rend2/tr_postprocess.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,490 @@
+/*
+===========================================================================
+Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete
+
+This file is part of Reaction source code.
+
+Reaction source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Reaction source code 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 Reaction source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+void RB_ToneMap(FBO_t *hdrFbo, int autoExposure)
+{
+	vec4i_t srcBox, dstBox;
+	vec4_t color;
+	static int lastFrameCount = 0;
+
+	if (autoExposure)
+	{
+		if (lastFrameCount == 0 || tr.frameCount < lastFrameCount || tr.frameCount - lastFrameCount > 5)
+		{
+			// determine average log luminance
+			FBO_t *srcFbo, *dstFbo, *tmp;
+			int size = 256;
+
+			lastFrameCount = tr.frameCount;
+
+			VectorSet4(dstBox, 0, 0, size, size);
+
+			srcFbo = hdrFbo;
+			dstFbo = tr.textureScratchFbo[0];
+			FBO_Blit(srcFbo, NULL, NULL, dstFbo, dstBox, &tr.calclevels4xShader[0], NULL, 0);
+
+			srcFbo = tr.textureScratchFbo[0];
+			dstFbo = tr.textureScratchFbo[1];
+
+			// downscale to 1x1 texture
+			while (size > 1)
+			{
+				VectorSet4(srcBox, 0, 0, size, size);
+				//size >>= 2;
+				size >>= 1;
+				VectorSet4(dstBox, 0, 0, size, size);
+
+				if (size == 1)
+					dstFbo = tr.targetLevelsFbo;
+
+				//FBO_Blit(targetFbo, srcBox, NULL, tr.textureScratchFbo[nextScratch], dstBox, &tr.calclevels4xShader[1], NULL, 0);
+				FBO_FastBlit(srcFbo, srcBox, dstFbo, dstBox, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+
+				tmp = srcFbo;
+				srcFbo = dstFbo;
+				dstFbo = tmp;
+			}
+		}
+
+		// blend with old log luminance for gradual change
+		VectorSet4(srcBox, 0, 0, 0, 0);
+
+		color[0] = 
+		color[1] =
+		color[2] = 1.0f;
+		if (glRefConfig.textureFloat)
+			color[3] = 0.03f;
+		else
+			color[3] = 0.1f;
+
+		FBO_Blit(tr.targetLevelsFbo, srcBox, NULL, tr.calcLevelsFbo, NULL,  NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+	}
+
+	// tonemap
+	color[0] =
+	color[1] =
+	color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value);
+	color[3] = 1.0f;
+
+	if (autoExposure)
+		GL_BindToTMU(tr.calcLevelsImage,  TB_LEVELSMAP);
+	else
+		GL_BindToTMU(tr.fixedLevelsImage, TB_LEVELSMAP);
+
+	FBO_Blit(hdrFbo, NULL, NULL, tr.screenScratchFbo, NULL, &tr.tonemapShader, color, 0);
+}
+
+
+void RB_BokehBlur(float blur)
+{
+//	vec4i_t srcBox, dstBox;
+	vec4_t color;
+	
+	blur *= 10.0f;
+
+	if (blur < 0.004f)
+		return;
+
+	if (glRefConfig.framebufferObject)
+	{
+		// bokeh blur
+		if (blur > 0.0f)
+		{
+			// create a quarter texture
+			FBO_Blit(tr.screenScratchFbo, NULL, NULL, tr.quarterFbo[0], NULL, NULL, NULL, 0);
+		}
+
+#ifndef HQ_BLUR
+		if (blur > 1.0f)
+		{
+			// create a 1/16th texture
+			FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.textureScratchFbo[0], NULL, NULL, NULL, 0);
+		}
+#endif
+
+		if (blur > 0.0f && blur <= 1.0f)
+		{
+			// Crossfade original with quarter texture
+			VectorSet4(color, 1, 1, 1, blur);
+
+			FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+		}
+#ifndef HQ_BLUR
+		// ok blur, but can see some pixelization
+		else if (blur > 1.0f && blur <= 2.0f)
+		{
+			// crossfade quarter texture with 1/16th texture
+			FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, NULL, 0);
+
+			VectorSet4(color, 1, 1, 1, blur - 1.0f);
+
+			FBO_Blit(tr.textureScratchFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+		}
+		else if (blur > 2.0f)
+		{
+			// blur 1/16th texture then replace
+			int i;
+
+			for (i = 0; i < 2; i++)
+			{
+				vec2_t blurTexScale;
+				float subblur;
+
+				subblur = ((blur - 2.0f) / 2.0f) / 3.0f * (float)(i + 1);
+
+				blurTexScale[0] =
+				blurTexScale[1] = subblur;
+
+				color[0] =
+				color[1] =
+				color[2] = 0.5f;
+				color[3] = 1.0f;
+
+				if (i != 0)
+					FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+				else
+					FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, 0);
+			}
+
+			FBO_Blit(tr.textureScratchFbo[1], NULL, NULL, tr.screenScratchFbo, NULL, &tr.textureColorShader, NULL, 0);
+		}
+#else // higher quality blur, but slower
+		else if (blur > 1.0f)
+		{
+			// blur quarter texture then replace
+			int i;
+
+			src = tr.quarterFbo[0];
+			dst = tr.quarterFbo[1];
+
+			VectorSet4(color, 0.5f, 0.5f, 0.5f, 1);
+
+			for (i = 0; i < 2; i++)
+			{
+				vec2_t blurTexScale;
+				float subblur;
+
+				subblur = (blur - 1.0f) / 2.0f * (float)(i + 1);
+
+				blurTexScale[0] =
+				blurTexScale[1] = subblur;
+
+				color[0] =
+				color[1] =
+				color[2] = 1.0f;
+				if (i != 0)
+					color[3] = 1.0f;
+				else
+					color[3] = 0.5f;
+
+				FBO_Blit(tr.quarterFbo[0], NULL, blurTexScale, tr.quarterFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+			}
+
+			FBO_Blit(tr.quarterFbo[1], NULL, NULL, tr.screenScratchFbo, NULL, &tr.textureColorShader, NULL, 0);
+		}
+#endif
+	}
+}
+
+
+#ifdef REACTION
+static void RB_RadialBlur(FBO_t *srcFbo, FBO_t *dstFbo, int passes, float stretch, float x, float y, float w, float h, float xcenter, float ycenter, float alpha)
+{
+	vec4i_t srcBox, dstBox;
+	vec4_t color;
+	const float inc = 1.f / passes;
+	const float mul = powf(stretch, inc);
+	float scale;
+
+	{
+		vec2_t texScale;
+
+		texScale[0] = 
+		texScale[1] = 1.0f;
+
+		alpha *= inc;
+		VectorSet4(color, alpha, alpha, alpha, 1.0f);
+
+		VectorSet4(srcBox, 0, 0, srcFbo->width, srcFbo->height);
+		VectorSet4(dstBox, x, y, w, h);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, 0);
+
+		--passes;
+		scale = mul;
+		while (passes > 0)
+		{
+			float iscale = 1.f / scale;
+			float s0 = xcenter * (1.f - iscale);
+			float t0 = (1.0f - ycenter) * (1.f - iscale);
+			float s1 = iscale + s0;
+			float t1 = iscale + t0;
+
+			srcBox[0] = s0 * srcFbo->width;
+			srcBox[1] = t0 * srcFbo->height;
+			srcBox[2] = (s1 - s0) * srcFbo->width;
+			srcBox[3] = (t1 - t0) * srcFbo->height;
+			
+			FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+			scale *= mul;
+			--passes;
+		}
+	}
+}
+
+
+static qboolean RB_UpdateSunFlareVis(void)
+{
+	GLuint sampleCount = 0;
+	if (!glRefConfig.occlusionQuery)
+		return qtrue;
+
+	tr.sunFlareQueryIndex ^= 1;
+	if (!tr.sunFlareQueryActive[tr.sunFlareQueryIndex])
+		return qtrue;
+
+	/* debug code */
+	if (0)
+	{
+		int iter;
+		for (iter=0 ; ; ++iter)
+		{
+			GLint available = 0;
+			qglGetQueryObjectivARB(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_AVAILABLE_ARB, &available);
+			if (available)
+				break;
+		}
+
+		ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter);
+	}
+	
+	qglGetQueryObjectuivARB(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_ARB, &sampleCount);
+	return sampleCount > 0;
+}
+
+void RB_GodRays(void)
+{
+	vec4i_t srcBox, dstBox;
+	vec4_t color;
+	vec3_t dir;
+	float dot;
+	const float cutoff = 0.25f;
+	qboolean colorize = qtrue;
+
+//	float w, h, w2, h2;
+	matrix_t mvp;
+	vec4_t pos, hpos;
+
+	if (!backEnd.viewHasSunFlare)
+		return;
+
+	VectorSubtract(backEnd.sunFlarePos, backEnd.viewParms.or.origin, dir);
+	VectorNormalize(dir);
+
+	dot = DotProduct(dir, backEnd.viewParms.or.axis[0]);
+	if (dot < cutoff)
+		return;
+
+	if (!RB_UpdateSunFlareVis())
+		return;
+
+	VectorCopy(backEnd.sunFlarePos, pos);
+	pos[3] = 1.f;
+
+	// project sun point
+	Matrix16Multiply(backEnd.viewParms.projectionMatrix, backEnd.viewParms.world.modelMatrix, mvp);
+	Matrix16Transform(mvp, pos, hpos);
+		
+	// transform to UV coords
+	hpos[3] = 0.5f / hpos[3];
+
+	pos[0] = 0.5f + hpos[0] * hpos[3];
+	pos[1] = 0.5f - hpos[1] * hpos[3];
+	
+	// viewport dimensions
+	// JBravo: Apparently not used
+/*	w = glConfig.vidWidth;
+	h = glConfig.vidHeight;
+	w2 = glConfig.vidWidth / 2;
+	h2 = glConfig.vidHeight / 2; */
+
+	// initialize quarter buffers
+	{
+		float mul = 1.f;
+		vec2_t texScale;
+
+		texScale[0] = 
+		texScale[1] = 1.0f;
+
+		VectorSet4(color, mul, mul, mul, 1);
+
+		// first, downsample the framebuffer
+		VectorSet4(srcBox, 0, 0, tr.godRaysFbo->width, tr.godRaysFbo->height);
+		VectorSet4(dstBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+		FBO_Blit(tr.godRaysFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, 0);
+		
+		if (colorize)
+		{
+			VectorSet4(srcBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height);
+			FBO_Blit(tr.screenScratchFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, 
+			         GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
+		}	
+	}
+
+	// radial blur passes, ping-ponging between the two quarter-size buffers
+	{
+		const float stretch_add = 2.f/3.f;
+		float stretch = 1.f + stretch_add;
+		int i;
+		for (i=0; i<2; ++i)
+		{
+			RB_RadialBlur(tr.quarterFbo[i&1], tr.quarterFbo[(~i) & 1], 5, stretch, 0.f, 0.f, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height, pos[0], pos[1], 1.125f);
+			stretch += stretch_add;
+		}
+	}
+	
+	// add result back on top of the main buffer
+	{
+		float mul = 1.f;
+		vec2_t texScale;
+
+		texScale[0] = 
+		texScale[1] = 1.0f;
+
+		VectorSet4(color, mul, mul, mul, 1);
+
+		VectorSet4(srcBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+		VectorSet4(dstBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height);
+		FBO_Blit(tr.quarterFbo[0], srcBox, texScale, tr.screenScratchFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+	}
+}
+#endif
+
+static void RB_BlurAxis(FBO_t *srcFbo, FBO_t *dstFbo, float strength, qboolean horizontal)
+{
+	float dx, dy;
+	float xmul, ymul;
+	float weights[3] = {
+		0.227027027f,
+		0.316216216f,
+		0.070270270f,
+	};
+	float offsets[3] = {
+		0.f,
+		1.3846153846f,
+		3.2307692308f,
+	};
+
+	xmul = horizontal;
+	ymul = 1.f - xmul;
+
+	xmul *= strength;
+	ymul *= strength;
+
+	{
+		vec4i_t srcBox, dstBox;
+		vec4_t color;
+		vec2_t texScale;
+
+		texScale[0] = 
+		texScale[1] = 1.0f;
+
+		VectorSet4(color, weights[0], weights[0], weights[0], 1.0f);
+		VectorSet4(srcBox, 0, 0, srcFbo->width, srcFbo->height);
+		VectorSet4(dstBox, 0, 0, dstFbo->width, dstFbo->height);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, 0 );
+
+		VectorSet4(color, weights[1], weights[1], weights[1], 1.0f);
+		dx = offsets[1] * xmul;
+		dy = offsets[1] * ymul;
+		VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+		VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+		VectorSet4(color, weights[2], weights[2], weights[2], 1.0f);
+		dx = offsets[2] * xmul;
+		dy = offsets[2] * ymul;
+		VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+		VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height);
+		FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+	}
+}
+
+static void RB_HBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength)
+{
+	RB_BlurAxis(srcFbo, dstFbo, strength, qtrue);
+}
+
+static void RB_VBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength)
+{
+	RB_BlurAxis(srcFbo, dstFbo, strength, qfalse);
+}
+
+void RB_GaussianBlur(float blur)
+{
+	//float mul = 1.f;
+	float factor = Com_Clamp(0.f, 1.f, blur);
+
+	if (factor <= 0.f)
+		return;
+
+	{
+		vec4i_t srcBox, dstBox;
+		vec4_t color;
+		vec2_t texScale;
+
+		texScale[0] = 
+		texScale[1] = 1.0f;
+
+		VectorSet4(color, 1, 1, 1, 1);
+
+		// first, downsample the framebuffer
+		VectorSet4(srcBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height);
+		VectorSet4(dstBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+		FBO_Blit(tr.screenScratchFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, 0);
+
+		VectorSet4(srcBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
+		VectorSet4(dstBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height);
+		FBO_Blit(tr.quarterFbo[0], srcBox, texScale, tr.textureScratchFbo[0], dstBox, &tr.textureColorShader, color, 0);
+
+		// set the alpha channel
+		VectorSet4(srcBox, 0, 0, tr.whiteImage->width, tr.whiteImage->height);
+		VectorSet4(dstBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height);
+		qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
+		FBO_BlitFromTexture(tr.whiteImage, srcBox, texScale, tr.textureScratchFbo[0], dstBox, &tr.textureColorShader, color, GLS_DEPTHTEST_DISABLE);
+		qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+		// blur the tiny buffer horizontally and vertically
+		RB_HBlur(tr.textureScratchFbo[0], tr.textureScratchFbo[1], factor);
+		RB_VBlur(tr.textureScratchFbo[1], tr.textureScratchFbo[0], factor);
+
+		// finally, merge back to framebuffer
+		VectorSet4(srcBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height);
+		VectorSet4(dstBox, 0, 0, tr.screenScratchFbo->width,     tr.screenScratchFbo->height);
+		color[3] = factor;
+		FBO_Blit(tr.textureScratchFbo[0], srcBox, texScale, tr.screenScratchFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+	}
+}

Added: trunk/code/rend2/tr_postprocess.h
===================================================================
--- trunk/code/rend2/tr_postprocess.h	                        (rev 0)
+++ trunk/code/rend2/tr_postprocess.h	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,33 @@
+/*
+===========================================================================
+Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete
+
+This file is part of Reaction source code.
+
+Reaction source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Reaction source code 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 Reaction source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#ifndef TR_POSTPROCESS_H
+#define TR_POSTPROCESS_H
+
+#include "tr_fbo.h"
+
+void RB_ToneMap(FBO_t *hdrFbo, int autoExposure);
+void RB_BokehBlur(float blur);
+void RB_GodRays(void);
+void RB_GaussianBlur(float blur);
+
+#endif
\ No newline at end of file

Added: trunk/code/rend2/tr_scene.c
===================================================================
--- trunk/code/rend2/tr_scene.c	                        (rev 0)
+++ trunk/code/rend2/tr_scene.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,539 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+int			r_firstSceneDrawSurf;
+
+int			r_numdlights;
+int			r_firstSceneDlight;
+
+int			r_numentities;
+int			r_firstSceneEntity;
+
+int			r_numpolys;
+int			r_firstScenePoly;
+
+int			r_numpolyverts;
+
+
+/*
+====================
+R_ToggleSmpFrame
+
+====================
+*/
+void R_ToggleSmpFrame( void ) {
+	if ( r_smp->integer ) {
+		// use the other buffers next frame, because another CPU
+		// may still be rendering into the current ones
+		tr.smpFrame ^= 1;
+	} else {
+		tr.smpFrame = 0;
+	}
+
+	backEndData[tr.smpFrame]->commands.used = 0;
+
+	r_firstSceneDrawSurf = 0;
+
+	r_numdlights = 0;
+	r_firstSceneDlight = 0;
+
+	r_numentities = 0;
+	r_firstSceneEntity = 0;
+
+	r_numpolys = 0;
+	r_firstScenePoly = 0;
+
+	r_numpolyverts = 0;
+}
+
+
+/*
+====================
+RE_ClearScene
+
+====================
+*/
+void RE_ClearScene( void ) {
+	r_firstSceneDlight = r_numdlights;
+	r_firstSceneEntity = r_numentities;
+	r_firstScenePoly = r_numpolys;
+}
+
+/*
+===========================================================================
+
+DISCRETE POLYS
+
+===========================================================================
+*/
+
+/*
+=====================
+R_AddPolygonSurfaces
+
+Adds all the scene's polys into this view's drawsurf list
+=====================
+*/
+void R_AddPolygonSurfaces( void ) {
+	int			i;
+	shader_t	*sh;
+	srfPoly_t	*poly;
+// JBravo: Fog fixes
+	int		fogMask;
+
+	tr.currentEntityNum = REFENTITYNUM_WORLD;
+	tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT;
+	fogMask = -((tr.refdef.rdflags & RDF_NOFOG) == 0);
+
+	for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) {
+		sh = R_GetShaderByHandle( poly->hShader );
+		R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex & fogMask, qfalse, qfalse );
+	}
+}
+
+/*
+=====================
+RE_AddPolyToScene
+
+=====================
+*/
+void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) {
+	srfPoly_t	*poly;
+	int			i, j;
+	int			fogIndex;
+	fog_t		*fog;
+	vec3_t		bounds[2];
+
+	if ( !tr.registered ) {
+		return;
+	}
+
+	if ( !hShader ) {
+		// This isn't a useful warning, and an hShader of zero isn't a null shader, it's
+		// the default shader.
+		//ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n");
+		//return;
+	}
+
+	for ( j = 0; j < numPolys; j++ ) {
+		if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) {
+      /*
+      NOTE TTimo this was initially a PRINT_WARNING
+      but it happens a lot with high fighting scenes and particles
+      since we don't plan on changing the const and making for room for those effects
+      simply cut this message to developer only
+      */
+			ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n");
+			return;
+		}
+
+		poly = &backEndData[tr.smpFrame]->polys[r_numpolys];
+		poly->surfaceType = SF_POLY;
+		poly->hShader = hShader;
+		poly->numVerts = numVerts;
+		poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts];
+		
+		Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) );
+
+		if ( glConfig.hardwareType == GLHW_RAGEPRO ) {
+			poly->verts->modulate[0] = 255;
+			poly->verts->modulate[1] = 255;
+			poly->verts->modulate[2] = 255;
+			poly->verts->modulate[3] = 255;
+		}
+		// done.
+		r_numpolys++;
+		r_numpolyverts += numVerts;
+
+		// if no world is loaded
+		if ( tr.world == NULL ) {
+			fogIndex = 0;
+		}
+		// see if it is in a fog volume
+		else if ( tr.world->numfogs == 1 ) {
+			fogIndex = 0;
+		} else {
+			// find which fog volume the poly is in
+			VectorCopy( poly->verts[0].xyz, bounds[0] );
+			VectorCopy( poly->verts[0].xyz, bounds[1] );
+			for ( i = 1 ; i < poly->numVerts ; i++ ) {
+				AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] );
+			}
+			for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) {
+				fog = &tr.world->fogs[fogIndex]; 
+				if ( bounds[1][0] >= fog->bounds[0][0]
+					&& bounds[1][1] >= fog->bounds[0][1]
+					&& bounds[1][2] >= fog->bounds[0][2]
+					&& bounds[0][0] <= fog->bounds[1][0]
+					&& bounds[0][1] <= fog->bounds[1][1]
+					&& bounds[0][2] <= fog->bounds[1][2] ) {
+					break;
+				}
+			}
+			if ( fogIndex == tr.world->numfogs ) {
+				fogIndex = 0;
+			}
+		}
+		poly->fogIndex = fogIndex;
+	}
+}
+
+
+//=================================================================================
+
+
+/*
+=====================
+RE_AddRefEntityToScene
+
+=====================
+*/
+void RE_AddRefEntityToScene( const refEntity_t *ent ) {
+#ifdef REACTION
+	// JBravo: Mirrored models
+	vec3_t cross;
+#endif
+
+	if ( !tr.registered ) {
+		return;
+	}
+	if ( r_numentities >= MAX_REFENTITIES ) {
+		ri.Printf(PRINT_DEVELOPER, "RE_AddRefEntityToScene: Dropping refEntity, reached MAX_REFENTITIES\n");
+		return;
+	}
+	if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) {
+		static qboolean firstTime = qtrue;
+		if (firstTime) {
+			firstTime = qfalse;
+			ri.Printf( PRINT_WARNING, "RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n");
+		}
+		return;
+	}
+	if ( (int)ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) {
+		ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType );
+	}
+
+	backEndData[tr.smpFrame]->entities[r_numentities].e = *ent;
+	backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse;
+
+#ifdef REACTION
+	// JBravo: Mirrored models
+	CrossProduct(ent->axis[0], ent->axis[1], cross);
+	backEndData[tr.smpFrame]->entities[r_numentities].mirrored = (DotProduct(ent->axis[2], cross) < 0.f);
+#endif
+
+	r_numentities++;
+}
+
+
+/*
+=====================
+RE_AddDynamicLightToScene
+
+=====================
+*/
+void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) {
+	dlight_t	*dl;
+
+	if ( !tr.registered ) {
+		return;
+	}
+	if ( r_numdlights >= MAX_DLIGHTS ) {
+		return;
+	}
+	if ( intensity <= 0 ) {
+		return;
+	}
+	// these cards don't have the correct blend mode
+	if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+		return;
+	}
+	dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++];
+	VectorCopy (org, dl->origin);
+	dl->radius = intensity;
+	dl->color[0] = r;
+	dl->color[1] = g;
+	dl->color[2] = b;
+	dl->additive = additive;
+}
+
+/*
+=====================
+RE_AddLightToScene
+
+=====================
+*/
+void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+	RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse );
+}
+
+/*
+=====================
+RE_AddAdditiveLightToScene
+
+=====================
+*/
+void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+	RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue );
+}
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+RE_RenderScene
+
+Draw a 3D view into a part of the window, then return
+to 2D drawing.
+
+Rendering a scene may require multiple views to be rendered
+to handle mirrors,
+@@@@@@@@@@@@@@@@@@@@@
+*/
+void RE_RenderScene( const refdef_t *fd ) {
+	viewParms_t		parms;
+	int				startTime;
+
+	if ( !tr.registered ) {
+		return;
+	}
+	GLimp_LogComment( "====== RE_RenderScene =====\n" );
+
+	if ( r_norefresh->integer ) {
+		return;
+	}
+
+	startTime = ri.Milliseconds();
+
+	if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) {
+		ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel");
+	}
+
+	Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) );
+
+	tr.refdef.x = fd->x;
+	tr.refdef.y = fd->y;
+	tr.refdef.width = fd->width;
+	tr.refdef.height = fd->height;
+	tr.refdef.fov_x = fd->fov_x;
+	tr.refdef.fov_y = fd->fov_y;
+
+	VectorCopy( fd->vieworg, tr.refdef.vieworg );
+	VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] );
+	VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] );
+	VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] );
+
+	tr.refdef.time = fd->time;
+	tr.refdef.rdflags = fd->rdflags;
+
+	// copy the areamask data over and note if it has changed, which
+	// will force a reset of the visible leafs even if the view hasn't moved
+	tr.refdef.areamaskModified = qfalse;
+	if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) {
+		int		areaDiff;
+		int		i;
+
+		// compare the area bits
+		areaDiff = 0;
+		for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) {
+			areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i];
+			((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i];
+		}
+
+		if ( areaDiff ) {
+			// a door just opened or something
+			tr.refdef.areamaskModified = qtrue;
+		}
+	}
+
+	tr.refdef.sunDir[3] = 0.0f;
+	tr.refdef.sunCol[3] = 1.0f;
+	tr.refdef.sunAmbCol[3] = 1.0f;
+
+	VectorCopy(tr.sunDirection, tr.refdef.sunDir);
+	if ( (tr.refdef.rdflags & RDF_NOWORLDMODEL) || !(r_depthPrepass->value) ){
+		tr.refdef.colorScale = 1.0f;
+		VectorSet(tr.refdef.sunCol, 0, 0, 0);
+		VectorSet(tr.refdef.sunAmbCol, 0, 0, 0);
+	}
+	else if (r_forceSun->integer == 1)
+	{
+		float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8);
+		tr.refdef.colorScale = r_forceSunMapLightScale->value;
+		VectorScale(tr.sunLight, scale * r_forceSunLightScale->value, tr.refdef.sunCol);
+		VectorScale(tr.sunLight, scale * r_forceSunAmbientScale->value, tr.refdef.sunAmbCol);
+	}
+	else
+	{
+		float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8);
+		tr.refdef.colorScale = tr.mapLightScale;
+		VectorScale(tr.sunLight,   scale, tr.refdef.sunCol);
+		VectorScale(tr.sunAmbient, scale, tr.refdef.sunAmbCol);
+	}
+
+	if (r_forceAutoExposure->integer)
+	{
+		tr.refdef.autoExposureMinMax[0] = r_forceAutoExposureMin->value;
+		tr.refdef.autoExposureMinMax[1] = r_forceAutoExposureMax->value;
+	}
+	else
+	{
+		tr.refdef.autoExposureMinMax[0] = tr.autoExposureMinMax[0];
+		tr.refdef.autoExposureMinMax[1] = tr.autoExposureMinMax[1];
+	}
+
+	if (r_forceToneMap->integer)
+	{
+		tr.refdef.toneMinAvgMaxLinear[0] = pow(2, r_forceToneMapMin->value);
+		tr.refdef.toneMinAvgMaxLinear[1] = pow(2, r_forceToneMapAvg->value);
+		tr.refdef.toneMinAvgMaxLinear[2] = pow(2, r_forceToneMapMax->value);
+	}
+	else
+	{
+		tr.refdef.toneMinAvgMaxLinear[0] = pow(2, tr.toneMinAvgMaxLevel[0]);
+		tr.refdef.toneMinAvgMaxLinear[1] = pow(2, tr.toneMinAvgMaxLevel[1]);
+		tr.refdef.toneMinAvgMaxLinear[2] = pow(2, tr.toneMinAvgMaxLevel[2]);
+	}
+
+//#ifdef REACTION
+	// Makro - copy exta info if present
+	if (fd->rdflags & RDF_EXTRA) {
+		const refdefex_t* extra = (const refdefex_t*) (fd+1);
+
+		tr.refdef.blurFactor = extra->blurFactor;
+
+		if (fd->rdflags & RDF_SUNLIGHT)
+		{
+			VectorCopy(extra->sunDir,    tr.refdef.sunDir);
+			VectorCopy(extra->sunCol,    tr.refdef.sunCol);
+			VectorCopy(extra->sunAmbCol, tr.refdef.sunAmbCol);
+		}
+	} 
+	else
+	{
+		tr.refdef.blurFactor = 0.0f;
+	}
+//#endif
+
+	// derived info
+
+	tr.refdef.floatTime = tr.refdef.time * 0.001f;
+
+	tr.refdef.numDrawSurfs = r_firstSceneDrawSurf;
+	tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs;
+
+	tr.refdef.num_entities = r_numentities - r_firstSceneEntity;
+	tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity];
+
+	tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight;
+	tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight];
+
+	tr.refdef.numPolys = r_numpolys - r_firstScenePoly;
+	tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly];
+
+	tr.refdef.num_pshadows = 0;
+	tr.refdef.pshadows = &backEndData[tr.smpFrame]->pshadows[0];
+
+	// turn off dynamic lighting globally by clearing all the
+	// dlights if it needs to be disabled or if vertex lighting is enabled
+	if ( r_dynamiclight->integer == 0 ||
+		 r_vertexLight->integer == 1 ||
+		 glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+		tr.refdef.num_dlights = 0;
+	}
+
+	// a single frame may have multiple scenes draw inside it --
+	// a 3D game view, 3D status bar renderings, 3D menus, etc.
+	// They need to be distinguished by the light flare code, because
+	// the visibility state for a given surface may be different in
+	// each scene / view.
+	tr.frameSceneNum++;
+	tr.sceneCount++;
+
+	// SmileTheory: playing with shadow mapping
+	if (!( fd->rdflags & RDF_NOWORLDMODEL ) && tr.refdef.num_dlights && r_dlightMode->integer >= 2)
+	{
+		R_RenderDlightCubemaps(fd);
+	}
+
+	/* playing with more shadows */
+	if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_shadows->integer == 4)
+	{
+		R_RenderPshadowMaps(fd);
+	}
+
+	// playing with even more shadows
+	if(!( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows))
+	{
+		R_RenderSunShadowMaps(fd, 0);
+		R_RenderSunShadowMaps(fd, 1);
+		R_RenderSunShadowMaps(fd, 2);
+	}
+
+	// setup view parms for the initial view
+	//
+	// set up viewport
+	// The refdef takes 0-at-the-top y coordinates, so
+	// convert to GL's 0-at-the-bottom space
+	//
+	Com_Memset( &parms, 0, sizeof( parms ) );
+	parms.viewportX = tr.refdef.x;
+	parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height );
+	parms.viewportWidth = tr.refdef.width;
+	parms.viewportHeight = tr.refdef.height;
+	parms.isPortal = qfalse;
+
+	parms.fovX = tr.refdef.fov_x;
+	parms.fovY = tr.refdef.fov_y;
+	
+	parms.stereoFrame = tr.refdef.stereoFrame;
+
+	if (glRefConfig.framebufferObject)
+	{
+		parms.targetFbo = tr.renderFbo;
+	}
+
+	VectorCopy( fd->vieworg, parms.or.origin );
+	VectorCopy( fd->viewaxis[0], parms.or.axis[0] );
+	VectorCopy( fd->viewaxis[1], parms.or.axis[1] );
+	VectorCopy( fd->viewaxis[2], parms.or.axis[2] );
+
+	VectorCopy( fd->vieworg, parms.pvsOrigin );
+
+	if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows))
+	{
+		parms.flags = VPF_USESUNLIGHT;
+	}
+
+	R_RenderView( &parms );
+
+	if(!( fd->rdflags & RDF_NOWORLDMODEL ))
+		R_AddPostProcessCmd();
+
+	// the next scene rendered in this frame will tack on after this one
+	r_firstSceneDrawSurf = tr.refdef.numDrawSurfs;
+	r_firstSceneEntity = r_numentities;
+	r_firstSceneDlight = r_numdlights;
+	r_firstScenePoly = r_numpolys;
+
+	tr.frontEndMsec += ri.Milliseconds() - startTime;
+}

Added: trunk/code/rend2/tr_shade.c
===================================================================
--- trunk/code/rend2/tr_shade.c	                        (rev 0)
+++ trunk/code/rend2/tr_shade.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1818 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_shade.c
+
+#include "tr_local.h" 
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+/*
+
+  THIS ENTIRE FILE IS BACK END
+
+  This file deals with applying shaders to surface data in the tess struct.
+*/
+
+
+/*
+==================
+R_DrawElements
+
+==================
+*/
+
+void R_DrawElementsVBO( int numIndexes, int firstIndex )
+{
+	if (glRefConfig.drawRangeElements)
+		qglDrawRangeElementsEXT(GL_TRIANGLES, 0, numIndexes, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE)));
+	else
+		qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE)));
+	
+}
+
+
+static void R_DrawMultiElementsVBO( int multiDrawPrimitives, const GLvoid **multiDrawFirstIndex, GLsizei *multiDrawNumIndexes )
+{
+	if (glRefConfig.multiDrawArrays)
+	{
+		qglMultiDrawElementsEXT(GL_TRIANGLES, multiDrawNumIndexes, GL_INDEX_TYPE, multiDrawFirstIndex, multiDrawPrimitives);
+	}
+	else
+	{
+		int i;
+
+		if (glRefConfig.drawRangeElements)
+		{
+			for (i = 0; i < multiDrawPrimitives; i++)
+			{
+				qglDrawRangeElementsEXT(GL_TRIANGLES, 0, multiDrawNumIndexes[i],  multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]);
+			}
+		}
+		else
+		{
+			for (i = 0; i < multiDrawPrimitives; i++)
+			{
+				qglDrawElements(GL_TRIANGLES, multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]);
+			}
+		}
+	}
+}
+
+
+/*
+=============================================================
+
+SURFACE SHADERS
+
+=============================================================
+*/
+
+shaderCommands_t	tess;
+
+
+/*
+=================
+R_BindAnimatedImageToTMU
+
+=================
+*/
+static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) {
+	int		index;
+
+	if ( bundle->isVideoMap ) {
+		int oldtmu = glState.currenttmu;
+		GL_SelectTexture(tmu);
+		ri.CIN_RunCinematic(bundle->videoMapHandle);
+		ri.CIN_UploadCinematic(bundle->videoMapHandle);
+		GL_SelectTexture(oldtmu);
+		return;
+	}
+
+	if ( bundle->numImageAnimations <= 1 ) {
+		GL_BindToTMU( bundle->image[0], tmu);
+		return;
+	}
+
+	// it is necessary to do this messy calc to make sure animations line up
+	// exactly with waveforms of the same frequency
+	index = ri.ftol(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE);
+	index >>= FUNCTABLE_SIZE2;
+
+	if ( index < 0 ) {
+		index = 0;	// may happen with shader time offsets
+	}
+	index %= bundle->numImageAnimations;
+
+	GL_BindToTMU( bundle->image[ index ], tmu );
+}
+
+
+/*
+================
+DrawTris
+
+Draws triangle outlines for debugging
+================
+*/
+static void DrawTris (shaderCommands_t *input) {
+	GL_Bind( tr.whiteImage );
+
+	GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
+	qglDepthRange( 0, 0 );
+
+	{
+		shaderProgram_t *sp = &tr.textureColorShader;
+		vec4_t color;
+
+		GLSL_VertexAttribsState(ATTR_POSITION);
+		GLSL_BindProgram(sp);
+		
+		GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		VectorSet4(color, 1, 1, 1, 1);
+		GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color);
+
+		if (input->multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+		}
+	}
+
+	qglDepthRange( 0, 1 );
+}
+
+
+/*
+================
+DrawNormals
+
+Draws vertex normals for debugging
+================
+*/
+static void DrawNormals (shaderCommands_t *input) {
+	//FIXME: implement this
+}
+
+/*
+==============
+RB_BeginSurface
+
+We must set some things up before beginning any tesselation,
+because a surface may be forced to perform a RB_End due
+to overflow.
+==============
+*/
+void RB_BeginSurface( shader_t *shader, int fogNum ) {
+
+	shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader;
+
+	tess.numIndexes = 0;
+	tess.firstIndex = 0;
+	tess.numVertexes = 0;
+	tess.multiDrawPrimitives = 0;
+	tess.shader = state;
+	tess.fogNum = fogNum;
+	tess.dlightBits = 0;		// will be OR'd in by surface functions
+	tess.pshadowBits = 0;       // will be OR'd in by surface functions
+	tess.xstages = state->stages;
+	tess.numPasses = state->numUnfoggedPasses;
+	tess.currentStageIteratorFunc = state->optimalStageIteratorFunc;
+	tess.useInternalVBO = qtrue;
+
+	tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+	if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) {
+		tess.shaderTime = tess.shader->clampTime;
+	}
+
+	if (backEnd.viewParms.flags & VPF_SHADOWMAP)
+	{
+		tess.currentStageIteratorFunc = RB_StageIteratorGeneric;
+	}
+}
+
+
+
+extern float EvalWaveForm( const waveForm_t *wf );
+extern float EvalWaveFormClamped( const waveForm_t *wf );
+
+
+static void ComputeTexMatrix( shaderStage_t *pStage, int bundleNum, float *outmatrix)
+{
+	int tm;
+	float matrix[16], currentmatrix[16];
+	textureBundle_t *bundle = &pStage->bundle[bundleNum];
+
+	Matrix16Identity(outmatrix);
+	Matrix16Identity(currentmatrix);
+
+	for ( tm = 0; tm < bundle->numTexMods ; tm++ ) {
+		switch ( bundle->texMods[tm].type )
+		{
+			
+		case TMOD_NONE:
+			tm = TR_MAX_TEXMODS;		// break out of for loop
+			break;
+
+		case TMOD_TURBULENT:
+			RB_CalcTurbulentTexMatrix( &bundle->texMods[tm].wave, 
+									 matrix );
+			outmatrix[12] = matrix[12];
+			outmatrix[13] = matrix[13];
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		case TMOD_ENTITY_TRANSLATE:
+			RB_CalcScrollTexMatrix( backEnd.currentEntity->e.shaderTexCoord,
+								 matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		case TMOD_SCROLL:
+			RB_CalcScrollTexMatrix( bundle->texMods[tm].scroll,
+									 matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		case TMOD_SCALE:
+			RB_CalcScaleTexMatrix( bundle->texMods[tm].scale,
+								  matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+		
+		case TMOD_STRETCH:
+			RB_CalcStretchTexMatrix( &bundle->texMods[tm].wave, 
+								   matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		case TMOD_TRANSFORM:
+			RB_CalcTransformTexMatrix( &bundle->texMods[tm],
+									 matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		case TMOD_ROTATE:
+			RB_CalcRotateTexMatrix( bundle->texMods[tm].rotateSpeed,
+									matrix );
+			Matrix16Multiply(matrix, currentmatrix, outmatrix);
+			Matrix16Copy(outmatrix, currentmatrix);
+			break;
+
+		default:
+			ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", bundle->texMods[tm].type, tess.shader->name );
+			break;
+		}
+	}
+}
+
+
+static void ComputeDeformValues(int *deformGen, vec5_t deformParams)
+{
+	// u_DeformGen
+	*deformGen = DGEN_NONE;
+	if(!ShaderRequiresCPUDeforms(tess.shader))
+	{
+		deformStage_t  *ds;
+
+		// only support the first one
+		ds = &tess.shader->deforms[0];
+
+		switch (ds->deformation)
+		{
+			case DEFORM_WAVE:
+				*deformGen = ds->deformationWave.func;
+
+				deformParams[0] = ds->deformationWave.base;
+				deformParams[1] = ds->deformationWave.amplitude;
+				deformParams[2] = ds->deformationWave.phase;
+				deformParams[3] = ds->deformationWave.frequency;
+				deformParams[4] = ds->deformationSpread;
+				break;
+
+			case DEFORM_BULGE:
+				*deformGen = DGEN_BULGE;
+
+				deformParams[0] = 0;
+				deformParams[1] = ds->bulgeHeight; // amplitude
+				deformParams[2] = ds->bulgeWidth;  // phase
+				deformParams[3] = ds->bulgeSpeed;  // frequency
+				deformParams[4] = 0;
+				break;
+
+			default:
+				break;
+		}
+	}
+}
+
+
+static void ProjectDlightTexture( void ) {
+	int		l;
+	vec3_t	origin;
+	float	scale;
+	float	radius;
+	int deformGen;
+	vec5_t deformParams;
+
+	if ( !backEnd.refdef.num_dlights ) {
+		return;
+	}
+
+	ComputeDeformValues(&deformGen, deformParams);
+
+	for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
+		dlight_t	*dl;
+		shaderProgram_t *sp;
+		vec4_t vector;
+
+		if ( !( tess.dlightBits & ( 1 << l ) ) ) {
+			continue;	// this surface definately doesn't have any of this light
+		}
+
+		dl = &backEnd.refdef.dlights[l];
+		VectorCopy( dl->transformed, origin );
+		radius = dl->radius;
+		scale = 1.0f / radius;
+
+		sp = &tr.dlightallShader;
+
+		backEnd.pc.c_dlightDraws++;
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+		GLSL_SetUniformFloat(sp, DLIGHT_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+		
+		GLSL_SetUniformInt(sp, DLIGHT_UNIFORM_DEFORMGEN, deformGen);
+		if (deformGen != DGEN_NONE)
+		{
+			GLSL_SetUniformFloat5(sp, DLIGHT_UNIFORM_DEFORMPARAMS, deformParams);
+			GLSL_SetUniformFloat(sp, DLIGHT_UNIFORM_TIME, tess.shaderTime);
+		}
+
+		vector[0] = dl->color[0];
+		vector[1] = dl->color[1];
+		vector[2] = dl->color[2];
+		vector[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, DLIGHT_UNIFORM_COLOR, vector);
+
+		vector[0] = origin[0];
+		vector[1] = origin[1];
+		vector[2] = origin[2];
+		vector[3] = scale;
+		GLSL_SetUniformVec4(sp, DLIGHT_UNIFORM_DLIGHTINFO, vector);
+	  
+		GL_Bind( tr.dlightImage );
+
+		// include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+		// where they aren't rendered
+		if ( dl->additive ) {
+			GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+		}
+		else {
+			GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+		}
+
+		if (tess.multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(tess.multiDrawPrimitives, (const GLvoid **)tess.multiDrawFirstIndex, tess.multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(tess.numIndexes, tess.firstIndex);
+		}
+
+		backEnd.pc.c_totalIndexes += tess.numIndexes;
+		backEnd.pc.c_dlightIndexes += tess.numIndexes;
+	}
+}
+
+
+static void ComputeShaderColors( shaderStage_t *pStage, vec4_t baseColor, vec4_t vertColor )
+{
+	//
+	// rgbGen
+	//
+	switch ( pStage->rgbGen )
+	{
+		case CGEN_IDENTITY:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] =
+			baseColor[3] = 1.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_IDENTITY_LIGHTING:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] = tr.identityLight;
+			baseColor[3] = 1.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_EXACT_VERTEX:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] =
+			baseColor[3] = 0.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 1.0f;
+			break;
+		case CGEN_EXACT_VERTEX_LIT:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] = 1.0f;
+			baseColor[3] = 0.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] = 0.0f;
+			vertColor[3] = 1.0f;
+			break;
+		case CGEN_CONST:
+			baseColor[0] = pStage->constantColor[0] / 255.0f;
+			baseColor[1] = pStage->constantColor[1] / 255.0f;
+			baseColor[2] = pStage->constantColor[2] / 255.0f;
+			baseColor[3] = pStage->constantColor[3] / 255.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_VERTEX:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] =
+			baseColor[3] = 0.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] = tr.identityLight;
+			vertColor[3] = 1.0f;
+			break;
+		case CGEN_VERTEX_LIT:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] = tr.identityLight;
+			baseColor[3] = 0.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] = 0.0f;
+			vertColor[3] = 1.0f;
+			break;
+		case CGEN_ONE_MINUS_VERTEX:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] = tr.identityLight;
+			baseColor[3] = 1.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] = -tr.identityLight;
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_FOG:
+			{
+				fog_t		*fog;
+
+				fog = tr.world->fogs + tess.fogNum;
+
+				baseColor[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f;
+				baseColor[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f;
+				baseColor[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f;
+				baseColor[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f;
+			}
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_WAVEFORM:
+			baseColor[0] = 
+			baseColor[1] = 
+			baseColor[2] = RB_CalcWaveColorSingle( &pStage->rgbWave );
+			baseColor[3] = 1.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_ENTITY:
+			if (backEnd.currentEntity)
+			{
+				baseColor[0] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f;
+				baseColor[1] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f;
+				baseColor[2] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f;
+				baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f;
+			}
+			
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_ONE_MINUS_ENTITY:
+			if (backEnd.currentEntity)
+			{
+				baseColor[0] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f;
+				baseColor[1] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f;
+				baseColor[2] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f;
+				baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f;
+			}
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+		case CGEN_LIGHTING_DIFFUSE:
+		case CGEN_BAD:
+			baseColor[0] = 
+			baseColor[1] =
+			baseColor[2] =
+			baseColor[3] = 1.0f;
+
+			vertColor[0] =
+			vertColor[1] =
+			vertColor[2] =
+			vertColor[3] = 0.0f;
+			break;
+	}
+
+	//
+	// alphaGen
+	//
+	switch ( pStage->alphaGen )
+	{
+		case AGEN_SKIP:
+			break;
+		case AGEN_IDENTITY:
+			baseColor[3] = 1.0f;
+			vertColor[3] = 0.0f;
+			break;
+		case AGEN_CONST:
+			baseColor[3] = pStage->constantColor[3] / 255.0f;
+			vertColor[3] = 0.0f;
+			break;
+		case AGEN_WAVEFORM:
+			baseColor[3] = RB_CalcWaveAlphaSingle( &pStage->alphaWave );
+			vertColor[3] = 0.0f;
+			break;
+		case AGEN_ENTITY:
+			if (backEnd.currentEntity)
+			{
+				baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f;
+			}
+			vertColor[3] = 0.0f;
+			break;
+		case AGEN_ONE_MINUS_ENTITY:
+			if (backEnd.currentEntity)
+			{
+				baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f;
+			}
+			vertColor[3] = 0.0f;
+			break;
+		case AGEN_VERTEX:
+			baseColor[3] = 0.0f;
+			vertColor[3] = 1.0f;
+			break;
+		case AGEN_ONE_MINUS_VERTEX:
+			baseColor[3] = 1.0f;
+			vertColor[3] = -1.0f;
+			break;
+		case AGEN_LIGHTING_SPECULAR:
+		case AGEN_PORTAL:
+		case AGEN_FRESNEL:
+			// Done entirely in vertex program
+			baseColor[3] = 1.0f;
+			vertColor[3] = 0.0f;
+			break;
+	}
+
+	// FIXME: find some way to implement this.
+#if 0
+	// if in greyscale rendering mode turn all color values into greyscale.
+	if(r_greyscale->integer)
+	{
+		int scale;
+		
+		for(i = 0; i < tess.numVertexes; i++)
+		{
+			scale = (tess.svars.colors[i][0] + tess.svars.colors[i][1] + tess.svars.colors[i][2]) / 3;
+			tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale;
+		}
+	}
+#endif
+}
+
+
+static void ComputeFogValues(vec4_t fogDistanceVector, vec4_t fogDepthVector, float *eyeT)
+{
+	// from RB_CalcFogTexCoords()
+	fog_t  *fog;
+	vec3_t  local;
+
+	if (!tess.fogNum)
+		return;
+
+	fog = tr.world->fogs + tess.fogNum;
+
+	VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local );
+	fogDistanceVector[0] = -backEnd.or.modelMatrix[2];
+	fogDistanceVector[1] = -backEnd.or.modelMatrix[6];
+	fogDistanceVector[2] = -backEnd.or.modelMatrix[10];
+	fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] );
+
+	// scale the fog vectors based on the fog's thickness
+	VectorScale4(fogDistanceVector, fog->tcScale, fogDistanceVector);
+
+	// rotate the gradient vector for this orientation
+	if ( fog->hasSurface ) {
+		fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + 
+			fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2];
+		fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + 
+			fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2];
+		fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + 
+			fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2];
+		fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface );
+
+		*eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3];
+	} else {
+		*eyeT = 1;	// non-surface fog always has eye inside
+	}
+}
+
+
+static void ComputeFogColorMask( shaderStage_t *pStage, vec4_t fogColorMask )
+{
+	switch(pStage->adjustColorsForFog)
+	{
+		case ACFF_MODULATE_RGB:
+			fogColorMask[0] =
+			fogColorMask[1] =
+			fogColorMask[2] = 1.0f;
+			fogColorMask[3] = 0.0f;
+			break;
+		case ACFF_MODULATE_ALPHA:
+			fogColorMask[0] =
+			fogColorMask[1] =
+			fogColorMask[2] = 0.0f;
+			fogColorMask[3] = 1.0f;
+			break;
+		case ACFF_MODULATE_RGBA:
+			fogColorMask[0] =
+			fogColorMask[1] =
+			fogColorMask[2] =
+			fogColorMask[3] = 1.0f;
+			break;
+		default:
+			fogColorMask[0] =
+			fogColorMask[1] =
+			fogColorMask[2] =
+			fogColorMask[3] = 0.0f;
+			break;
+	}
+}
+
+
+static void ForwardDlight( void ) {
+	int		l;
+	//vec3_t	origin;
+	//float	scale;
+	float	radius;
+
+	int deformGen;
+	vec5_t deformParams;
+	
+	vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+	float eyeT = 0;
+
+	shaderCommands_t *input = &tess;
+	shaderStage_t *pStage = tess.xstages[0];
+
+	if ( !backEnd.refdef.num_dlights ) {
+		return;
+	}
+	
+	ComputeDeformValues(&deformGen, deformParams);
+
+	ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+	for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
+		dlight_t	*dl;
+		shaderProgram_t *sp;
+		vec4_t vector;
+		matrix_t matrix;
+
+		if ( !( tess.dlightBits & ( 1 << l ) ) ) {
+			continue;	// this surface definately doesn't have any of this light
+		}
+
+		dl = &backEnd.refdef.dlights[l];
+		//VectorCopy( dl->transformed, origin );
+		radius = dl->radius;
+		//scale = 1.0f / radius;
+
+		//if (pStage->glslShaderGroup == tr.lightallShader)
+		{
+			int index = pStage->glslShaderIndex;
+
+			index &= ~(LIGHTDEF_LIGHTTYPE_MASK | LIGHTDEF_USE_DELUXEMAP);
+			index |= LIGHTDEF_USE_LIGHT_VECTOR;
+
+			sp = &tr.lightallShader[index];
+		}
+
+		backEnd.pc.c_lightallDraws++;
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen);
+		if (deformGen != DGEN_NONE)
+		{
+			GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime);
+		}
+
+		if ( input->fogNum ) {
+			vec4_t fogColorMask;
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT);
+
+			ComputeFogColorMask(pStage, fogColorMask);
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask);
+		}
+
+		{
+			vec4_t baseColor;
+			vec4_t vertColor;
+
+			ComputeShaderColors(pStage, baseColor, vertColor);
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor);
+		}
+
+		if (pStage->alphaGen == AGEN_PORTAL)
+		{
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange);
+		}
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen);
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen);
+
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, dl->color);
+
+		VectorSet(vector, 0, 0, 0);
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT, vector);
+
+		VectorCopy(dl->origin, vector);
+		vector[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vector);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, radius);
+
+		GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo);
+		
+		// include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+		// where they aren't rendered
+		GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix);
+
+		if (pStage->bundle[TB_DIFFUSEMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP);
+
+		if (pStage->bundle[TB_NORMALMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP);
+
+		if (pStage->bundle[TB_SPECULARMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP);
+
+		if (r_dlightMode->integer >= 2)
+		{
+			GL_SelectTexture(TB_SHADOWMAP);
+			GL_BindCubemap(tr.shadowCubemaps[l]);
+			GL_SelectTexture(0);
+		}
+
+		ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix );
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix);
+
+		//
+		// draw
+		//
+
+		if (input->multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+		}
+
+		backEnd.pc.c_totalIndexes += tess.numIndexes;
+		backEnd.pc.c_dlightIndexes += tess.numIndexes;
+	}
+}
+
+
+static void ForwardSunlight( void ) {
+//	int		l;
+	//vec3_t	origin;
+	//float	scale;
+	int stage;
+	int stageGlState[2];
+	qboolean alphaOverride = qfalse;
+
+	int deformGen;
+	vec5_t deformParams;
+	
+	vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+	float eyeT = 0;
+
+	shaderCommands_t *input = &tess;
+	
+	ComputeDeformValues(&deformGen, deformParams);
+
+	ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+	// deal with vertex alpha blended surfaces
+	if (input->xstages[0] && input->xstages[1] && 
+		(input->xstages[1]->alphaGen == AGEN_VERTEX || input->xstages[1]->alphaGen == AGEN_ONE_MINUS_VERTEX))
+	{
+		stageGlState[0] = input->xstages[0]->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS);
+
+		if (stageGlState[0] == 0 || stageGlState[0] == (GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO))
+		{
+			stageGlState[1] = input->xstages[1]->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS);
+
+			if (stageGlState[1] == (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA))
+			{
+				alphaOverride = qtrue;
+				stageGlState[0] = GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
+				stageGlState[1] = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
+			}
+			else if (stageGlState[1] == (GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_SRC_ALPHA))
+			{
+				alphaOverride = qtrue;
+				stageGlState[0] = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
+				stageGlState[1] = GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
+			}
+		}
+	}
+
+	if (!alphaOverride)
+	{
+		stageGlState[0] =
+		stageGlState[1] = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
+	}
+
+	for ( stage = 0; stage < 2 /*MAX_SHADER_STAGES */; stage++ )
+	{
+		shaderStage_t *pStage = input->xstages[stage];
+		shaderProgram_t *sp;
+		//vec4_t vector;
+		matrix_t matrix;
+
+		if ( !pStage )
+		{
+			break;
+		}
+
+		//VectorCopy( dl->transformed, origin );
+
+		//if (pStage->glslShaderGroup == tr.lightallShader)
+		{
+			int index = pStage->glslShaderIndex;
+
+			index &= ~(LIGHTDEF_LIGHTTYPE_MASK | LIGHTDEF_USE_DELUXEMAP);
+			index |= LIGHTDEF_USE_LIGHT_VECTOR | LIGHTDEF_USE_SHADOWMAP;
+
+			if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+			{
+				index |= LIGHTDEF_ENTITY;
+			}
+
+			sp = &tr.lightallShader[index];
+		}
+
+		backEnd.pc.c_lightallDraws++;
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen);
+		if (deformGen != DGEN_NONE)
+		{
+			GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime);
+		}
+
+		if ( input->fogNum ) {
+			vec4_t fogColorMask;
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT);
+
+			ComputeFogColorMask(pStage, fogColorMask);
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask);
+		}
+
+		{
+			vec4_t baseColor;
+			vec4_t vertColor;
+
+			ComputeShaderColors(pStage, baseColor, vertColor);
+
+			if (alphaOverride)
+			{
+				if (input->xstages[1]->alphaGen == AGEN_VERTEX)
+				{
+					baseColor[3] = 0.0f;
+					vertColor[3] = 1.0f;
+				}
+				else if (input->xstages[1]->alphaGen == AGEN_ONE_MINUS_VERTEX)
+				{
+					baseColor[3] = 1.0f;
+					vertColor[3] = -1.0f;
+				}
+			}
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor);
+		}
+
+		if (pStage->alphaGen == AGEN_PORTAL)
+		{
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange);
+		}
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen);
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen);
+
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, backEnd.refdef.sunCol);
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT,  backEnd.refdef.sunAmbCol);
+
+		GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, backEnd.refdef.sunDir);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, 9999999999.9f);
+
+		GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo);
+		
+		GL_State( stageGlState[stage] );
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix);
+
+		if (pStage->bundle[TB_DIFFUSEMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP);
+
+		if (pStage->bundle[TB_NORMALMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP);
+
+		if (pStage->bundle[TB_SPECULARMAP].image[0])
+			R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP);
+
+		/*
+		{
+			GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP);
+			GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2);
+			GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3);
+			GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]);
+			GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]);
+			GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]);
+		}
+		*/
+		GL_BindToTMU(tr.screenShadowImage, TB_SHADOWMAP);
+
+		ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix );
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix);
+
+		//
+		// draw
+		//
+
+		if (input->multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+		}
+
+		backEnd.pc.c_totalIndexes += tess.numIndexes;
+		backEnd.pc.c_dlightIndexes += tess.numIndexes;
+	}
+}
+
+
+static void ProjectPshadowVBOGLSL( void ) {
+	int		l;
+	vec3_t	origin;
+	float	radius;
+
+	int deformGen;
+	vec5_t deformParams;
+
+	shaderCommands_t *input = &tess;
+
+	if ( !backEnd.refdef.num_pshadows ) {
+		return;
+	}
+	
+	ComputeDeformValues(&deformGen, deformParams);
+
+	for ( l = 0 ; l < backEnd.refdef.num_pshadows ; l++ ) {
+		pshadow_t	*ps;
+		shaderProgram_t *sp;
+		vec4_t vector;
+
+		if ( !( tess.pshadowBits & ( 1 << l ) ) ) {
+			continue;	// this surface definately doesn't have any of this shadow
+		}
+
+		ps = &backEnd.refdef.pshadows[l];
+		VectorCopy( ps->lightOrigin, origin );
+		radius = ps->lightRadius;
+
+		sp = &tr.pshadowShader;
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+		VectorCopy(origin, vector);
+		vector[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, PSHADOW_UNIFORM_LIGHTORIGIN, vector);
+
+		VectorScale(ps->lightViewAxis[0], 1.0f / ps->viewRadius, vector);
+		GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTFORWARD, vector);
+
+		VectorScale(ps->lightViewAxis[1], 1.0f / ps->viewRadius, vector);
+		GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTRIGHT, vector);
+
+		VectorScale(ps->lightViewAxis[2], 1.0f / ps->viewRadius, vector);
+		GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTUP, vector);
+
+		GLSL_SetUniformFloat(sp, PSHADOW_UNIFORM_LIGHTRADIUS, radius);
+	  
+		// include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+		// where they aren't rendered
+		GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
+
+		GL_BindToTMU( tr.pshadowMaps[l], TB_DIFFUSEMAP );
+
+		//
+		// draw
+		//
+
+		if (input->multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+		}
+
+		backEnd.pc.c_totalIndexes += tess.numIndexes;
+		//backEnd.pc.c_dlightIndexes += tess.numIndexes;
+	}
+}
+
+
+
+/*
+===================
+RB_FogPass
+
+Blends a fog texture on top of everything else
+===================
+*/
+static void RB_FogPass( void ) {
+	fog_t		*fog;
+	vec4_t  color;
+	vec4_t	fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+	float	eyeT = 0;
+	shaderProgram_t *sp = &tr.fogShader;
+
+	int deformGen;
+	vec5_t deformParams;
+
+	ComputeDeformValues(&deformGen, deformParams);
+
+	backEnd.pc.c_fogDraws++;
+
+	GLSL_BindProgram(sp);
+
+	fog = tr.world->fogs + tess.fogNum;
+
+	GLSL_SetUniformMatrix16(sp, FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+	GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+	
+	GLSL_SetUniformInt(sp, FOGPASS_UNIFORM_DEFORMGEN, deformGen);
+	if (deformGen != DGEN_NONE)
+	{
+		GLSL_SetUniformFloat5(sp, FOGPASS_UNIFORM_DEFORMPARAMS, deformParams);
+		GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_TIME, tess.shaderTime);
+	}
+
+	color[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f;
+	color[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f;
+	color[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f;
+	color[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f;
+	GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_COLOR, color);
+
+	ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+	GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_FOGDISTANCE, fogDistanceVector);
+	GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_FOGDEPTH, fogDepthVector);
+	GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_FOGEYET, eyeT);
+
+	if ( tess.shader->fogPass == FP_EQUAL ) {
+		GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
+	} else {
+		GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
+	}
+
+	if (tess.multiDrawPrimitives)
+	{
+		R_DrawMultiElementsVBO(tess.multiDrawPrimitives, (const GLvoid **)tess.multiDrawFirstIndex, tess.multiDrawNumIndexes);
+	}
+	else
+	{
+		R_DrawElementsVBO(tess.numIndexes, tess.firstIndex);
+	}
+}
+
+
+static unsigned int RB_CalcShaderVertexAttribs( shaderCommands_t *input )
+{
+	unsigned int vertexAttribs = input->shader->vertexAttribs;
+
+	if(glState.vertexAttribsInterpolation > 0.0f)
+	{
+		vertexAttribs |= ATTR_POSITION2;
+		if (vertexAttribs & ATTR_NORMAL)
+		{
+			vertexAttribs |= ATTR_NORMAL2;
+#ifdef USE_VERT_TANGENT_SPACE
+			vertexAttribs |= ATTR_TANGENT2;
+			vertexAttribs |= ATTR_BITANGENT2;
+#endif
+		}
+	}
+
+	return vertexAttribs;
+}
+
+static void RB_IterateStagesGeneric( shaderCommands_t *input )
+{
+	int stage;
+	matrix_t matrix;
+	
+	vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+	float eyeT = 0;
+
+	int deformGen;
+	vec5_t deformParams;
+
+	ComputeDeformValues(&deformGen, deformParams);
+
+	ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+	for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
+	{
+		shaderStage_t *pStage = input->xstages[stage];
+		shaderProgram_t *sp;
+
+		if ( !pStage )
+		{
+			break;
+		}
+
+		if (backEnd.depthFill)
+		{
+			if (pStage->glslShaderGroup)
+			{
+				int index = 0;
+
+				if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+				{
+					index |= LIGHTDEF_ENTITY;
+				}
+
+				sp = &pStage->glslShaderGroup[index];
+			}
+			else
+			{
+				int shaderAttribs = 0;
+
+				if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader))
+				{
+					shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES;
+				}
+
+				if (glState.vertexAttribsInterpolation > 0.0f && backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+				{
+					shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION;
+				}
+
+				sp = &tr.genericShader[shaderAttribs];
+			}
+		}
+		else if (pStage->glslShaderGroup)
+		{
+			int index = pStage->glslShaderIndex;
+
+			if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+			{
+				index |= LIGHTDEF_ENTITY;
+			}
+
+			if (r_lightmap->integer && index & LIGHTDEF_USE_LIGHTMAP)
+			{
+				index = LIGHTDEF_USE_LIGHTMAP;
+			}
+
+			sp = &pStage->glslShaderGroup[index];
+
+			if (pStage->glslShaderGroup == tr.lightallShader)
+			{
+				backEnd.pc.c_lightallDraws++;
+			}
+		}
+		else
+		{
+			sp = GLSL_GetGenericShaderProgram(stage);
+
+			backEnd.pc.c_genericDraws++;
+		}
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+		
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen);
+		if (deformGen != DGEN_NONE)
+		{
+			GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime);
+		}
+
+		if ( input->fogNum ) {
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT);
+		}
+
+		GL_State( pStage->stateBits );
+
+		{
+			vec4_t baseColor;
+			vec4_t vertColor;
+			qboolean tint = qtrue;
+			int stage2;
+
+			ComputeShaderColors(pStage, baseColor, vertColor);
+
+			for ( stage2 = stage + 1; stage2 < MAX_SHADER_STAGES; stage2++ )
+			{
+				shaderStage_t *pStage2 = input->xstages[stage2];
+				unsigned int srcBlendBits, dstBlendBits;
+
+				if ( !pStage2 )
+				{
+					break;
+				}
+
+				srcBlendBits = pStage2->stateBits & GLS_SRCBLEND_BITS;
+				dstBlendBits = pStage2->stateBits & GLS_DSTBLEND_BITS;
+
+				if (srcBlendBits == GLS_SRCBLEND_DST_COLOR)
+				{
+					tint = qfalse;
+					break;
+				}
+			}
+			
+			if (!((tr.sunShadows || r_forceSun->integer) && tess.shader->sort <= SS_OPAQUE 
+				&& !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) && tess.xstages[0]->glslShaderGroup == tr.lightallShader))
+			{
+				tint = qfalse;
+			}
+
+			if (tint)
+			{
+				// use VectorScale to only scale first three values, not alpha
+				VectorScale(baseColor, backEnd.refdef.colorScale, baseColor);
+				VectorScale(vertColor, backEnd.refdef.colorScale, vertColor);
+			}
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor);
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor);
+		}
+
+		if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE)
+		{
+			vec4_t vec;
+
+			VectorScale(backEnd.currentEntity->ambientLight, 1.0f / 255.0f, vec);
+			GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT, vec);
+
+			VectorScale(backEnd.currentEntity->directedLight, 1.0f / 255.0f, vec);
+			GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, vec);
+			
+			VectorCopy(backEnd.currentEntity->lightDir, vec);
+			vec[3] = 0.0f;
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vec);
+
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, 999999.0f);
+		}
+
+		if (pStage->alphaGen == AGEN_PORTAL)
+		{
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange);
+		}
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen);
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen);
+
+		if ( input->fogNum )
+		{
+			vec4_t fogColorMask;
+
+			ComputeFogColorMask(pStage, fogColorMask);
+
+			GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask);
+		}
+
+		ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix );
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix);
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TCGEN0, pStage->bundle[0].tcGen);
+		if (pStage->bundle[0].tcGen == TCGEN_VECTOR)
+		{
+			vec3_t vec;
+
+			VectorCopy(pStage->bundle[0].tcGenVectors[0], vec);
+			GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_TCGEN0VECTOR0, vec);
+			VectorCopy(pStage->bundle[0].tcGenVectors[1], vec);
+			GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_TCGEN0VECTOR1, vec);
+		}
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix);
+
+		GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo);
+
+		//GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_MAPLIGHTSCALE, backEnd.refdef.mapLightScale);
+
+		//
+		// do multitexture
+		//
+		if ( backEnd.depthFill )
+		{
+			if (!(pStage->stateBits & GLS_ATEST_BITS))
+				GL_BindToTMU( tr.whiteImage, 0 );
+			else if ( pStage->bundle[TB_COLORMAP].image[0] != 0 )
+				R_BindAnimatedImageToTMU( &pStage->bundle[TB_COLORMAP], TB_COLORMAP );
+		}
+		else if ( pStage->glslShaderGroup )
+		{
+			int i;
+
+			if ((r_lightmap->integer == 1 || r_lightmap->integer == 2) && pStage->bundle[TB_LIGHTMAP].image[0])
+			{
+				for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+				{
+					if (i == TB_LIGHTMAP)
+					{
+						R_BindAnimatedImageToTMU( &pStage->bundle[i], i);
+					}
+					else if (pStage->bundle[i].image[0])
+					{
+						GL_BindToTMU( tr.whiteImage, i);
+					}
+				}
+			}
+			else if (r_lightmap->integer == 3 && pStage->bundle[TB_DELUXEMAP].image[0])
+			{
+				for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+				{
+					if (i == TB_LIGHTMAP)
+					{
+						R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], i);
+					}
+					else if (pStage->bundle[i].image[0])
+					{
+						GL_BindToTMU( tr.whiteImage, i);
+					}
+				}
+			}
+			else
+			{
+				for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+				{
+					if (pStage->bundle[i].image[0])
+					{
+						R_BindAnimatedImageToTMU( &pStage->bundle[i], i);
+					}
+				}
+			}
+		}
+		else if ( pStage->bundle[1].image[0] != 0 )
+		{
+			R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 );
+
+			//
+			// lightmap/secondary pass
+			//
+			if ( r_lightmap->integer ) {
+				GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, GL_REPLACE);
+			} else {
+				GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, tess.shader->multitextureEnv);
+			}
+
+			R_BindAnimatedImageToTMU( &pStage->bundle[1], 1 );
+		}
+		else 
+		{
+			//
+			// set state
+			//
+			if ( pStage->bundle[0].vertexLightmap && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer )
+			{
+				GL_BindToTMU( tr.whiteImage, 0 );
+			}
+			else 
+				R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 );
+
+			GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, 0);
+		}
+
+		//
+		// draw
+		//
+		if (input->multiDrawPrimitives)
+		{
+			R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+		}
+		else
+		{
+			R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+		}
+
+		// allow skipping out to show just lightmaps during development
+		if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) )
+		{
+			break;
+		}
+
+		if (backEnd.depthFill)
+			break;
+	}
+}
+
+
+static void RB_RenderShadowmap( shaderCommands_t *input )
+{
+	int deformGen;
+	vec5_t deformParams;
+
+	ComputeDeformValues(&deformGen, deformParams);
+
+	{
+		shaderProgram_t *sp = &tr.shadowmapShader;
+
+		vec4_t vector;
+
+		GLSL_BindProgram(sp);
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix);
+
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+		GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen);
+		if (deformGen != DGEN_NONE)
+		{
+			GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams);
+			GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime);
+		}
+
+		VectorCopy(backEnd.viewParms.or.origin, vector);
+		vector[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vector);
+		GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, backEnd.viewParms.zFar);
+
+		GL_State( 0 );
+
+		//
+		// do multitexture
+		//
+		//if ( pStage->glslShaderGroup )
+		{
+			//
+			// draw
+			//
+
+			if (input->multiDrawPrimitives)
+			{
+				R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes);
+			}
+			else
+			{
+				R_DrawElementsVBO(input->numIndexes, input->firstIndex);
+			}
+		}
+	}
+}
+
+
+
+/*
+** RB_StageIteratorGeneric
+*/
+void RB_StageIteratorGeneric( void )
+{
+	shaderCommands_t *input;
+	unsigned int vertexAttribs = 0;
+
+	input = &tess;
+	
+	if (!input->numVertexes || !input->numIndexes)
+	{
+		return;
+	}
+
+	if (tess.useInternalVBO)
+	{
+		RB_DeformTessGeometry();
+	}
+
+	vertexAttribs = RB_CalcShaderVertexAttribs( input );
+
+	if (tess.useInternalVBO)
+	{
+		RB_UpdateVBOs(vertexAttribs);
+	}
+	else
+	{
+		backEnd.pc.c_staticVboDraws++;
+	}
+
+	//
+	// log this call
+	//
+	if ( r_logFile->integer ) 
+	{
+		// don't just call LogComment, or we will get
+		// a call to va() every frame!
+		GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) );
+	}
+
+	//
+	// set face culling appropriately
+	//
+	if ((backEnd.viewParms.flags & VPF_DEPTHSHADOW))
+	{
+		//GL_Cull( CT_TWO_SIDED );
+		
+		if (input->shader->cullType == CT_TWO_SIDED)
+			GL_Cull( CT_TWO_SIDED );
+		else if (input->shader->cullType == CT_FRONT_SIDED)
+			GL_Cull( CT_BACK_SIDED );
+		else
+			GL_Cull( CT_FRONT_SIDED );
+		
+	}
+	else
+		GL_Cull( input->shader->cullType );
+
+	// set polygon offset if necessary
+	if ( input->shader->polygonOffset )
+	{
+		qglEnable( GL_POLYGON_OFFSET_FILL );
+		qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
+	}
+
+	//
+	// Set vertex attribs and pointers
+	//
+	GLSL_VertexAttribsState(vertexAttribs);
+
+	//
+	// render depth if in depthfill mode
+	//
+	if (backEnd.depthFill)
+	{
+		RB_IterateStagesGeneric( input );
+
+		//
+		// reset polygon offset
+		//
+		if ( input->shader->polygonOffset )
+		{
+			qglDisable( GL_POLYGON_OFFSET_FILL );
+		}
+
+		return;
+	}
+
+	//
+	// render shadowmap if in shadowmap mode
+	//
+	if (backEnd.viewParms.flags & VPF_SHADOWMAP)
+	{
+		if ( input->shader->sort == SS_OPAQUE )
+		{
+			RB_RenderShadowmap( input );
+		}
+		//
+		// reset polygon offset
+		//
+		if ( input->shader->polygonOffset )
+		{
+			qglDisable( GL_POLYGON_OFFSET_FILL );
+		}
+
+		return;
+	}
+
+	//
+	//
+	// call shader function
+	//
+	RB_IterateStagesGeneric( input );
+
+	//
+	// pshadows!
+	//
+	if ( tess.pshadowBits && tess.shader->sort <= SS_OPAQUE
+		&& !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) {
+		ProjectPshadowVBOGLSL();
+	}
+
+
+	// 
+	// now do any dynamic lighting needed
+	//
+	if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE
+		&& !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) {
+		if (tess.shader->numUnfoggedPasses == 1 && tess.xstages[0]->glslShaderGroup == tr.lightallShader
+			&& (tess.xstages[0]->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && r_dlightMode->integer)
+		{
+			ForwardDlight();
+		}
+		else
+		{
+			ProjectDlightTexture();
+		}
+	}
+
+	if ((backEnd.viewParms.flags & VPF_USESUNLIGHT) && tess.shader->sort <= SS_OPAQUE 
+	//if ((tr.sunShadows || r_forceSunlight->value > 0.0f) && tess.shader->sort <= SS_OPAQUE 
+	    && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) && tess.xstages[0]->glslShaderGroup == tr.lightallShader) {
+		ForwardSunlight();
+	}
+
+	//
+	// now do fog
+	//
+	if ( tess.fogNum && tess.shader->fogPass ) {
+		RB_FogPass();
+	}
+
+	//
+	// reset polygon offset
+	//
+	if ( input->shader->polygonOffset )
+	{
+		qglDisable( GL_POLYGON_OFFSET_FILL );
+	}
+}
+
+
+/*
+** RB_EndSurface
+*/
+void RB_EndSurface( void ) {
+	shaderCommands_t *input;
+
+	input = &tess;
+
+	if (input->numIndexes == 0 || input->numVertexes == 0) {
+		return;
+	}
+
+	if (input->indexes[SHADER_MAX_INDEXES-1] != 0) {
+		ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit");
+	}	
+	if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) {
+		ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit");
+	}
+
+	if ( tess.shader == tr.shadowShader ) {
+		RB_ShadowTessEnd();
+		return;
+	}
+
+	// for debugging of sort order issues, stop rendering after a given sort value
+	if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) {
+		return;
+	}
+
+	//
+	// update performance counters
+	//
+	backEnd.pc.c_shaders++;
+	backEnd.pc.c_vertexes += tess.numVertexes;
+	backEnd.pc.c_indexes += tess.numIndexes;
+	backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses;
+
+	//
+	// call off to shader specific tess end function
+	//
+	tess.currentStageIteratorFunc();
+
+	//
+	// draw debugging stuff
+	//
+	if ( r_showtris->integer ) {
+		DrawTris (input);
+	}
+	if ( r_shownormals->integer ) {
+		DrawNormals (input);
+	}
+	// clear shader so we can tell we don't have any unclosed surfaces
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+	tess.multiDrawPrimitives = 0;
+
+	GLimp_LogComment( "----------\n" );
+}

Added: trunk/code/rend2/tr_shade_calc.c
===================================================================
--- trunk/code/rend2/tr_shade_calc.c	                        (rev 0)
+++ trunk/code/rend2/tr_shade_calc.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1339 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_shade_calc.c
+
+#include "tr_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+
+#define	WAVEVALUE( table, base, amplitude, phase, freq )  ((base) + table[ ri.ftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude))
+
+static float *TableForFunc( genFunc_t func ) 
+{
+	switch ( func )
+	{
+	case GF_SIN:
+		return tr.sinTable;
+	case GF_TRIANGLE:
+		return tr.triangleTable;
+	case GF_SQUARE:
+		return tr.squareTable;
+	case GF_SAWTOOTH:
+		return tr.sawToothTable;
+	case GF_INVERSE_SAWTOOTH:
+		return tr.inverseSawToothTable;
+	case GF_NONE:
+	default:
+		break;
+	}
+
+	ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'", func, tess.shader->name );
+	return NULL;
+}
+
+/*
+** EvalWaveForm
+**
+** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly
+*/
+static float EvalWaveForm( const waveForm_t *wf ) 
+{
+	float	*table;
+
+	table = TableForFunc( wf->func );
+
+	return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency );
+}
+
+static float EvalWaveFormClamped( const waveForm_t *wf )
+{
+	float glow  = EvalWaveForm( wf );
+
+	if ( glow < 0 )
+	{
+		return 0;
+	}
+
+	if ( glow > 1 )
+	{
+		return 1;
+	}
+
+	return glow;
+}
+
+/*
+** RB_CalcStretchTexCoords
+*/
+void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st )
+{
+	float p;
+	texModInfo_t tmi;
+
+	p = 1.0f / EvalWaveForm( wf );
+
+	tmi.matrix[0][0] = p;
+	tmi.matrix[1][0] = 0;
+	tmi.translate[0] = 0.5f - 0.5f * p;
+
+	tmi.matrix[0][1] = 0;
+	tmi.matrix[1][1] = p;
+	tmi.translate[1] = 0.5f - 0.5f * p;
+
+	RB_CalcTransformTexCoords( &tmi, st );
+}
+
+void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix )
+{
+	float p;
+	texModInfo_t tmi;
+
+	p = 1.0f / EvalWaveForm( wf );
+
+	tmi.matrix[0][0] = p;
+	tmi.matrix[1][0] = 0;
+	tmi.translate[0] = 0.5f - 0.5f * p;
+
+	tmi.matrix[0][1] = 0;
+	tmi.matrix[1][1] = p;
+	tmi.translate[1] = 0.5f - 0.5f * p;
+
+	RB_CalcTransformTexMatrix( &tmi, matrix );
+}
+
+/*
+====================================================================
+
+DEFORMATIONS
+
+====================================================================
+*/
+
+/*
+========================
+RB_CalcDeformVertexes
+
+========================
+*/
+void RB_CalcDeformVertexes( deformStage_t *ds )
+{
+	int i;
+	vec3_t	offset;
+	float	scale;
+	float	*xyz = ( float * ) tess.xyz;
+	float	*normal = ( float * ) tess.normal;
+	float	*table;
+
+	if ( ds->deformationWave.frequency == 0 )
+	{
+		scale = EvalWaveForm( &ds->deformationWave );
+
+		for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
+		{
+			VectorScale( normal, scale, offset );
+			
+			xyz[0] += offset[0];
+			xyz[1] += offset[1];
+			xyz[2] += offset[2];
+		}
+	}
+	else
+	{
+		table = TableForFunc( ds->deformationWave.func );
+
+		for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
+		{
+			float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread;
+
+			scale = WAVEVALUE( table, ds->deformationWave.base, 
+				ds->deformationWave.amplitude,
+				ds->deformationWave.phase + off,
+				ds->deformationWave.frequency );
+
+			VectorScale( normal, scale, offset );
+			
+			xyz[0] += offset[0];
+			xyz[1] += offset[1];
+			xyz[2] += offset[2];
+		}
+	}
+}
+
+/*
+=========================
+RB_CalcDeformNormals
+
+Wiggle the normals for wavy environment mapping
+=========================
+*/
+void RB_CalcDeformNormals( deformStage_t *ds ) {
+	int i;
+	float	scale;
+	float	*xyz = ( float * ) tess.xyz;
+	float	*normal = ( float * ) tess.normal;
+
+	for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) {
+		scale = 0.98f;
+		scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+			tess.shaderTime * ds->deformationWave.frequency );
+		normal[ 0 ] += ds->deformationWave.amplitude * scale;
+
+		scale = 0.98f;
+		scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+			tess.shaderTime * ds->deformationWave.frequency );
+		normal[ 1 ] += ds->deformationWave.amplitude * scale;
+
+		scale = 0.98f;
+		scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+			tess.shaderTime * ds->deformationWave.frequency );
+		normal[ 2 ] += ds->deformationWave.amplitude * scale;
+
+		VectorNormalizeFast( normal );
+	}
+}
+
+/*
+========================
+RB_CalcBulgeVertexes
+
+========================
+*/
+void RB_CalcBulgeVertexes( deformStage_t *ds ) {
+	int i;
+	const float *st = ( const float * ) tess.texCoords[0];
+	float		*xyz = ( float * ) tess.xyz;
+	float		*normal = ( float * ) tess.normal;
+	float		now;
+
+	now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f;
+
+	for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) {
+		int		off;
+		float scale;
+
+		off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now );
+
+		scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight;
+			
+		xyz[0] += normal[0] * scale;
+		xyz[1] += normal[1] * scale;
+		xyz[2] += normal[2] * scale;
+	}
+}
+
+
+/*
+======================
+RB_CalcMoveVertexes
+
+A deformation that can move an entire surface along a wave path
+======================
+*/
+void RB_CalcMoveVertexes( deformStage_t *ds ) {
+	int			i;
+	float		*xyz;
+	float		*table;
+	float		scale;
+	vec3_t		offset;
+
+	table = TableForFunc( ds->deformationWave.func );
+
+	scale = WAVEVALUE( table, ds->deformationWave.base, 
+		ds->deformationWave.amplitude,
+		ds->deformationWave.phase,
+		ds->deformationWave.frequency );
+
+	VectorScale( ds->moveVector, scale, offset );
+
+	xyz = ( float * ) tess.xyz;
+	for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) {
+		VectorAdd( xyz, offset, xyz );
+	}
+}
+
+
+/*
+=============
+DeformText
+
+Change a polygon into a bunch of text polygons
+=============
+*/
+void DeformText( const char *text ) {
+	int		i;
+	vec3_t	origin, width, height;
+	int		len;
+	int		ch;
+	float	color[4];
+	float	bottom, top;
+	vec3_t	mid;
+
+	height[0] = 0;
+	height[1] = 0;
+	height[2] = -1;
+	CrossProduct( tess.normal[0], height, width );
+
+	// find the midpoint of the box
+	VectorClear( mid );
+	bottom = 999999;
+	top = -999999;
+	for ( i = 0 ; i < 4 ; i++ ) {
+		VectorAdd( tess.xyz[i], mid, mid );
+		if ( tess.xyz[i][2] < bottom ) {
+			bottom = tess.xyz[i][2];
+		}
+		if ( tess.xyz[i][2] > top ) {
+			top = tess.xyz[i][2];
+		}
+	}
+	VectorScale( mid, 0.25f, origin );
+
+	// determine the individual character size
+	height[0] = 0;
+	height[1] = 0;
+	height[2] = ( top - bottom ) * 0.5f;
+
+	VectorScale( width, height[2] * -0.75f, width );
+
+	// determine the starting position
+	len = strlen( text );
+	VectorMA( origin, (len-1), width, origin );
+
+	// clear the shader indexes
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+
+	color[0] = color[1] = color[2] = color[3] = 1.0f;
+
+	// draw each character
+	for ( i = 0 ; i < len ; i++ ) {
+		ch = text[i];
+		ch &= 255;
+
+		if ( ch != ' ' ) {
+			int		row, col;
+			float	frow, fcol, size;
+
+			row = ch>>4;
+			col = ch&15;
+
+			frow = row*0.0625f;
+			fcol = col*0.0625f;
+			size = 0.0625f;
+
+			RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size );
+		}
+		VectorMA( origin, -2, width, origin );
+	}
+}
+
+/*
+==================
+GlobalVectorToLocal
+==================
+*/
+static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) {
+	out[0] = DotProduct( in, backEnd.or.axis[0] );
+	out[1] = DotProduct( in, backEnd.or.axis[1] );
+	out[2] = DotProduct( in, backEnd.or.axis[2] );
+}
+
+/*
+=====================
+AutospriteDeform
+
+Assuming all the triangles for this shader are independant
+quads, rebuild them as forward facing sprites
+=====================
+*/
+static void AutospriteDeform( void ) {
+	int		i;
+	int		oldVerts;
+	float	*xyz;
+	vec3_t	mid, delta;
+	float	radius;
+	vec3_t	left, up;
+	vec3_t	leftDir, upDir;
+
+	if ( tess.numVertexes & 3 ) {
+		ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name );
+	}
+	if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) {
+		ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name );
+	}
+
+	oldVerts = tess.numVertexes;
+	tess.numVertexes = 0;
+	tess.numIndexes = 0;
+	tess.firstIndex = 0;
+
+	if ( backEnd.currentEntity != &tr.worldEntity ) {
+		GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir );
+		GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir );
+	} else {
+		VectorCopy( backEnd.viewParms.or.axis[1], leftDir );
+		VectorCopy( backEnd.viewParms.or.axis[2], upDir );
+	}
+
+	for ( i = 0 ; i < oldVerts ; i+=4 ) {
+		// find the midpoint
+		xyz = tess.xyz[i];
+
+		mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]);
+		mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]);
+		mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]);
+
+		VectorSubtract( xyz, mid, delta );
+		radius = VectorLength( delta ) * 0.707f;		// / sqrt(2)
+
+		VectorScale( leftDir, radius, left );
+		VectorScale( upDir, radius, up );
+
+		if ( backEnd.viewParms.isMirror ) {
+			VectorSubtract( vec3_origin, left, left );
+		}
+
+	  // compensate for scale in the axes if necessary
+  	if ( backEnd.currentEntity->e.nonNormalizedAxes ) {
+      float axisLength;
+		  axisLength = VectorLength( backEnd.currentEntity->e.axis[0] );
+  		if ( !axisLength ) {
+	  		axisLength = 0;
+  		} else {
+	  		axisLength = 1.0f / axisLength;
+  		}
+      VectorScale(left, axisLength, left);
+      VectorScale(up, axisLength, up);
+    }
+
+		RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] );
+	}
+}
+
+
+/*
+=====================
+Autosprite2Deform
+
+Autosprite2 will pivot a rectangular quad along the center of its long axis
+=====================
+*/
+int edgeVerts[6][2] = {
+	{ 0, 1 },
+	{ 0, 2 },
+	{ 0, 3 },
+	{ 1, 2 },
+	{ 1, 3 },
+	{ 2, 3 }
+};
+
+static void Autosprite2Deform( void ) {
+	int		i, j, k;
+	int		indexes;
+	float	*xyz;
+	vec3_t	forward;
+
+	if ( tess.numVertexes & 3 ) {
+		ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name );
+	}
+	if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) {
+		ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name );
+	}
+
+	if ( backEnd.currentEntity != &tr.worldEntity ) {
+		GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward );
+	} else {
+		VectorCopy( backEnd.viewParms.or.axis[0], forward );
+	}
+
+	// this is a lot of work for two triangles...
+	// we could precalculate a lot of it is an issue, but it would mess up
+	// the shader abstraction
+	for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) {
+		float	lengths[2];
+		int		nums[2];
+		vec3_t	mid[2];
+		vec3_t	major, minor;
+		float	*v1, *v2;
+
+		// find the midpoint
+		xyz = tess.xyz[i];
+
+		// identify the two shortest edges
+		nums[0] = nums[1] = 0;
+		lengths[0] = lengths[1] = 999999;
+
+		for ( j = 0 ; j < 6 ; j++ ) {
+			float	l;
+			vec3_t	temp;
+
+			v1 = xyz + 4 * edgeVerts[j][0];
+			v2 = xyz + 4 * edgeVerts[j][1];
+
+			VectorSubtract( v1, v2, temp );
+			
+			l = DotProduct( temp, temp );
+			if ( l < lengths[0] ) {
+				nums[1] = nums[0];
+				lengths[1] = lengths[0];
+				nums[0] = j;
+				lengths[0] = l;
+			} else if ( l < lengths[1] ) {
+				nums[1] = j;
+				lengths[1] = l;
+			}
+		}
+
+		for ( j = 0 ; j < 2 ; j++ ) {
+			v1 = xyz + 4 * edgeVerts[nums[j]][0];
+			v2 = xyz + 4 * edgeVerts[nums[j]][1];
+
+			mid[j][0] = 0.5f * (v1[0] + v2[0]);
+			mid[j][1] = 0.5f * (v1[1] + v2[1]);
+			mid[j][2] = 0.5f * (v1[2] + v2[2]);
+		}
+
+		// find the vector of the major axis
+		VectorSubtract( mid[1], mid[0], major );
+
+		// cross this with the view direction to get minor axis
+		CrossProduct( major, forward, minor );
+		VectorNormalize( minor );
+		
+		// re-project the points
+		for ( j = 0 ; j < 2 ; j++ ) {
+			float	l;
+
+			v1 = xyz + 4 * edgeVerts[nums[j]][0];
+			v2 = xyz + 4 * edgeVerts[nums[j]][1];
+
+			l = 0.5 * sqrt( lengths[j] );
+			
+			// we need to see which direction this edge
+			// is used to determine direction of projection
+			for ( k = 0 ; k < 5 ; k++ ) {
+				if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0]
+					&& tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) {
+					break;
+				}
+			}
+
+			if ( k == 5 ) {
+				VectorMA( mid[j], l, minor, v1 );
+				VectorMA( mid[j], -l, minor, v2 );
+			} else {
+				VectorMA( mid[j], -l, minor, v1 );
+				VectorMA( mid[j], l, minor, v2 );
+			}
+		}
+	}
+}
+
+
+/*
+=====================
+RB_DeformTessGeometry
+
+=====================
+*/
+void RB_DeformTessGeometry( void ) {
+	int		i;
+	deformStage_t	*ds;
+
+	if(!ShaderRequiresCPUDeforms(tess.shader))
+	{
+		// we don't need the following CPU deforms
+		return;
+	}
+
+	for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) {
+		ds = &tess.shader->deforms[ i ];
+
+		switch ( ds->deformation ) {
+        case DEFORM_NONE:
+            break;
+		case DEFORM_NORMALS:
+			RB_CalcDeformNormals( ds );
+			break;
+		case DEFORM_WAVE:
+			RB_CalcDeformVertexes( ds );
+			break;
+		case DEFORM_BULGE:
+			RB_CalcBulgeVertexes( ds );
+			break;
+		case DEFORM_MOVE:
+			RB_CalcMoveVertexes( ds );
+			break;
+		case DEFORM_PROJECTION_SHADOW:
+			RB_ProjectionShadowDeform();
+			break;
+		case DEFORM_AUTOSPRITE:
+			AutospriteDeform();
+			break;
+		case DEFORM_AUTOSPRITE2:
+			Autosprite2Deform();
+			break;
+		case DEFORM_TEXT0:
+		case DEFORM_TEXT1:
+		case DEFORM_TEXT2:
+		case DEFORM_TEXT3:
+		case DEFORM_TEXT4:
+		case DEFORM_TEXT5:
+		case DEFORM_TEXT6:
+		case DEFORM_TEXT7:
+			DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] );
+			break;
+		}
+	}
+}
+
+/*
+====================================================================
+
+COLORS
+
+====================================================================
+*/
+
+
+/*
+** RB_CalcColorFromEntity
+*/
+void RB_CalcColorFromEntity( unsigned char *dstColors )
+{
+	int	i;
+	int *pColors = ( int * ) dstColors;
+	int c;
+
+	if ( !backEnd.currentEntity )
+		return;
+
+	c = * ( int * ) backEnd.currentEntity->e.shaderRGBA;
+
+	for ( i = 0; i < tess.numVertexes; i++, pColors++ )
+	{
+		*pColors = c;
+	}
+}
+
+/*
+** RB_CalcColorFromOneMinusEntity
+*/
+void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors )
+{
+	int	i;
+	int *pColors = ( int * ) dstColors;
+	unsigned char invModulate[4];
+	int c;
+
+	if ( !backEnd.currentEntity )
+		return;
+
+	invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0];
+	invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1];
+	invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2];
+	invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3];	// this trashes alpha, but the AGEN block fixes it
+
+	c = * ( int * ) invModulate;
+
+	for ( i = 0; i < tess.numVertexes; i++, pColors++ )
+	{
+		*pColors = c;
+	}
+}
+
+/*
+** RB_CalcAlphaFromEntity
+*/
+void RB_CalcAlphaFromEntity( unsigned char *dstColors )
+{
+	int	i;
+
+	if ( !backEnd.currentEntity )
+		return;
+
+	dstColors += 3;
+
+	for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+	{
+		*dstColors = backEnd.currentEntity->e.shaderRGBA[3];
+	}
+}
+
+/*
+** RB_CalcAlphaFromOneMinusEntity
+*/
+void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors )
+{
+	int	i;
+
+	if ( !backEnd.currentEntity )
+		return;
+
+	dstColors += 3;
+
+	for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+	{
+		*dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3];
+	}
+}
+
+/*
+** RB_CalcWaveColorSingle
+*/
+float RB_CalcWaveColorSingle( const waveForm_t *wf )
+{
+	float glow;
+
+	if ( wf->func == GF_NOISE ) {
+		glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude;
+	} else {
+		glow = EvalWaveForm( wf ) * tr.identityLight;
+	}
+	
+	if ( glow < 0 ) {
+		glow = 0;
+	}
+	else if ( glow > 1 ) {
+		glow = 1;
+	}
+
+	return glow;
+}
+
+/*
+** RB_CalcWaveColor
+*/
+void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors )
+{
+	int i;
+	int v;
+	float glow;
+	int *colors = ( int * ) dstColors;
+	byte	color[4];
+
+	glow = RB_CalcWaveColorSingle( wf );
+
+	v = ri.ftol(255 * glow);
+	color[0] = color[1] = color[2] = v;
+	color[3] = 255;
+	v = *(int *)color;
+	
+	for ( i = 0; i < tess.numVertexes; i++, colors++ ) {
+		*colors = v;
+	}
+}
+
+/*
+** RB_CalcWaveAlphaSingle
+*/
+float RB_CalcWaveAlphaSingle( const waveForm_t *wf )
+{
+	return EvalWaveFormClamped( wf );
+}
+
+/*
+** RB_CalcWaveAlpha
+*/
+void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors )
+{
+	int i;
+	int v;
+	float glow;
+
+	glow = EvalWaveFormClamped( wf );
+
+	v = 255 * glow;
+
+	for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+	{
+		dstColors[3] = v;
+	}
+}
+
+/*
+** RB_CalcModulateColorsByFog
+*/
+void RB_CalcModulateColorsByFog( unsigned char *colors ) {
+	int		i;
+	float	texCoords[SHADER_MAX_VERTEXES][2];
+
+	// calculate texcoords so we can derive density
+	// this is not wasted, because it would only have
+	// been previously called if the surface was opaque
+	RB_CalcFogTexCoords( texCoords[0] );
+
+	for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+		float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+		colors[0] *= f;
+		colors[1] *= f;
+		colors[2] *= f;
+	}
+}
+
+/*
+** RB_CalcModulateAlphasByFog
+*/
+void RB_CalcModulateAlphasByFog( unsigned char *colors ) {
+	int		i;
+	float	texCoords[SHADER_MAX_VERTEXES][2];
+
+	// calculate texcoords so we can derive density
+	// this is not wasted, because it would only have
+	// been previously called if the surface was opaque
+	RB_CalcFogTexCoords( texCoords[0] );
+
+	for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+		float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+		colors[3] *= f;
+	}
+}
+
+/*
+** RB_CalcModulateRGBAsByFog
+*/
+void RB_CalcModulateRGBAsByFog( unsigned char *colors ) {
+	int		i;
+	float	texCoords[SHADER_MAX_VERTEXES][2];
+
+	// calculate texcoords so we can derive density
+	// this is not wasted, because it would only have
+	// been previously called if the surface was opaque
+	RB_CalcFogTexCoords( texCoords[0] );
+
+	for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+		float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+		colors[0] *= f;
+		colors[1] *= f;
+		colors[2] *= f;
+		colors[3] *= f;
+	}
+}
+
+
+/*
+====================================================================
+
+TEX COORDS
+
+====================================================================
+*/
+
+/*
+========================
+RB_CalcFogTexCoords
+
+To do the clipped fog plane really correctly, we should use
+projected textures, but I don't trust the drivers and it
+doesn't fit our shader data.
+========================
+*/
+void RB_CalcFogTexCoords( float *st ) {
+	int			i;
+	float		*v;
+	float		s, t;
+	float		eyeT;
+	qboolean	eyeOutside;
+	fog_t		*fog;
+	vec3_t		local;
+	vec4_t		fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+
+	fog = tr.world->fogs + tess.fogNum;
+
+	// all fogging distance is based on world Z units
+	VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local );
+	fogDistanceVector[0] = -backEnd.or.modelMatrix[2];
+	fogDistanceVector[1] = -backEnd.or.modelMatrix[6];
+	fogDistanceVector[2] = -backEnd.or.modelMatrix[10];
+	fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] );
+
+	// scale the fog vectors based on the fog's thickness
+	fogDistanceVector[0] *= fog->tcScale;
+	fogDistanceVector[1] *= fog->tcScale;
+	fogDistanceVector[2] *= fog->tcScale;
+	fogDistanceVector[3] *= fog->tcScale;
+
+	// rotate the gradient vector for this orientation
+	if ( fog->hasSurface ) {
+		fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + 
+			fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2];
+		fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + 
+			fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2];
+		fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + 
+			fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2];
+		fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface );
+
+		eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3];
+	} else {
+		eyeT = 1;	// non-surface fog always has eye inside
+	}
+
+	// see if the viewpoint is outside
+	// this is needed for clipping distance even for constant fog
+
+	if ( eyeT < 0 ) {
+		eyeOutside = qtrue;
+	} else {
+		eyeOutside = qfalse;
+	}
+
+	fogDistanceVector[3] += 1.0/512;
+
+	// calculate density for each point
+	for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) {
+		// calculate the length in fog
+		s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3];
+		t = DotProduct( v, fogDepthVector ) + fogDepthVector[3];
+
+		// partially clipped fogs use the T axis		
+		if ( eyeOutside ) {
+			if ( t < 1.0 ) {
+				t = 1.0/32;	// point is outside, so no fogging
+			} else {
+				t = 1.0/32 + 30.0/32 * t / ( t - eyeT );	// cut the distance at the fog plane
+			}
+		} else {
+			if ( t < 0 ) {
+				t = 1.0/32;	// point is outside, so no fogging
+			} else {
+				t = 31.0/32;
+			}
+		}
+
+		st[0] = s;
+		st[1] = t;
+		st += 2;
+	}
+}
+
+
+
+/*
+** RB_CalcEnvironmentTexCoords
+*/
+void RB_CalcEnvironmentTexCoords( float *st ) 
+{
+	int			i;
+	float		*v, *normal;
+	vec3_t		viewer, reflected;
+	float		d;
+
+	v = tess.xyz[0];
+	normal = tess.normal[0];
+
+	for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) 
+	{
+		VectorSubtract (backEnd.or.viewOrigin, v, viewer);
+		VectorNormalizeFast (viewer);
+
+		d = DotProduct (normal, viewer);
+
+		reflected[0] = normal[0]*2*d - viewer[0];
+		reflected[1] = normal[1]*2*d - viewer[1];
+		reflected[2] = normal[2]*2*d - viewer[2];
+
+		st[0] = 0.5 + reflected[1] * 0.5;
+		st[1] = 0.5 - reflected[2] * 0.5;
+	}
+}
+
+/*
+** RB_CalcTurbulentTexCoords
+*/
+void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st )
+{
+	int i;
+	float now;
+
+	now = ( wf->phase + tess.shaderTime * wf->frequency );
+
+	for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+	{
+		float s = st[0];
+		float t = st[1];
+
+		st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
+		st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
+	}
+}
+
+void RB_CalcTurbulentTexMatrix( const waveForm_t *wf, matrix_t matrix )
+{
+	float now;
+
+	now = ( wf->phase + tess.shaderTime * wf->frequency );
+
+	// bit of a hack here, hide amplitude and now in the matrix
+	// the vertex program will extract them and perform a turbulent pass last if it's nonzero
+
+	matrix[ 0] = 1.0f; matrix[ 4] = 0.0f; matrix[ 8] = 0.0f; matrix[12] = wf->amplitude;
+	matrix[ 1] = 0.0f; matrix[ 5] = 1.0f; matrix[ 9] = 0.0f; matrix[13] = now;
+	matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f; matrix[14] = 0.0f;
+	matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f; matrix[15] = 1.0f;
+}
+
+/*
+** RB_CalcScaleTexCoords
+*/
+void RB_CalcScaleTexCoords( const float scale[2], float *st )
+{
+	int i;
+
+	for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+	{
+		st[0] *= scale[0];
+		st[1] *= scale[1];
+	}
+}
+
+void RB_CalcScaleTexMatrix( const float scale[2], float *matrix )
+{
+	matrix[ 0] = scale[0]; matrix[ 4] = 0.0f;     matrix[ 8] = 0.0f; matrix[12] = 0.0f;
+	matrix[ 1] = 0.0f;     matrix[ 5] = scale[1]; matrix[ 9] = 0.0f; matrix[13] = 0.0f;
+	matrix[ 2] = 0.0f;     matrix[ 6] = 0.0f;     matrix[10] = 1.0f; matrix[14] = 0.0f;
+	matrix[ 3] = 0.0f;     matrix[ 7] = 0.0f;     matrix[11] = 0.0f; matrix[15] = 1.0f;
+}
+
+/*
+** RB_CalcScrollTexCoords
+*/
+void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st )
+{
+	int i;
+	float timeScale = tess.shaderTime;
+	float adjustedScrollS, adjustedScrollT;
+
+	adjustedScrollS = scrollSpeed[0] * timeScale;
+	adjustedScrollT = scrollSpeed[1] * timeScale;
+
+	// clamp so coordinates don't continuously get larger, causing problems
+	// with hardware limits
+	adjustedScrollS = adjustedScrollS - floor( adjustedScrollS );
+	adjustedScrollT = adjustedScrollT - floor( adjustedScrollT );
+
+	for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+	{
+		st[0] += adjustedScrollS;
+		st[1] += adjustedScrollT;
+	}
+}
+
+void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix )
+{
+	float timeScale = tess.shaderTime;
+	float adjustedScrollS, adjustedScrollT;
+
+	adjustedScrollS = scrollSpeed[0] * timeScale;
+	adjustedScrollT = scrollSpeed[1] * timeScale;
+
+	// clamp so coordinates don't continuously get larger, causing problems
+	// with hardware limits
+	adjustedScrollS = adjustedScrollS - floor( adjustedScrollS );
+	adjustedScrollT = adjustedScrollT - floor( adjustedScrollT );
+
+
+	matrix[ 0] = 1.0f; matrix[ 4] = 0.0f; matrix[ 8] = adjustedScrollS; matrix[12] = 0.0f;
+	matrix[ 1] = 0.0f; matrix[ 5] = 1.0f; matrix[ 9] = adjustedScrollT; matrix[13] = 0.0f;
+	matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f;            matrix[14] = 0.0f;
+	matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f;            matrix[15] = 1.0f;
+}
+
+/*
+** RB_CalcTransformTexCoords
+*/
+void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st  )
+{
+	int i;
+
+	for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+	{
+		float s = st[0];
+		float t = st[1];
+
+		st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0];
+		st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1];
+	}
+}
+
+void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix  )
+{
+	matrix[ 0] = tmi->matrix[0][0]; matrix[ 4] = tmi->matrix[1][0]; matrix[ 8] = tmi->translate[0]; matrix[12] = 0.0f;
+	matrix[ 1] = tmi->matrix[0][1]; matrix[ 5] = tmi->matrix[1][1]; matrix[ 9] = tmi->translate[1]; matrix[13] = 0.0f;
+	matrix[ 2] = 0.0f;              matrix[ 6] = 0.0f;              matrix[10] = 1.0f;              matrix[14] = 0.0f;
+	matrix[ 3] = 0.0f;              matrix[ 7] = 0.0f;              matrix[11] = 0.0f;              matrix[15] = 1.0f;
+}
+
+/*
+** RB_CalcRotateTexCoords
+*/
+void RB_CalcRotateTexCoords( float degsPerSecond, float *st )
+{
+	float timeScale = tess.shaderTime;
+	float degs;
+	int index;
+	float sinValue, cosValue;
+	texModInfo_t tmi;
+
+	degs = -degsPerSecond * timeScale;
+	index = degs * ( FUNCTABLE_SIZE / 360.0f );
+
+	sinValue = tr.sinTable[ index & FUNCTABLE_MASK ];
+	cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ];
+
+	tmi.matrix[0][0] = cosValue;
+	tmi.matrix[1][0] = -sinValue;
+	tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;
+
+	tmi.matrix[0][1] = sinValue;
+	tmi.matrix[1][1] = cosValue;
+	tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;
+
+	RB_CalcTransformTexCoords( &tmi, st );
+}
+
+void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix )
+{
+	float timeScale = tess.shaderTime;
+	float degs;
+	int index;
+	float sinValue, cosValue;
+	texModInfo_t tmi;
+
+	degs = -degsPerSecond * timeScale;
+	index = degs * ( FUNCTABLE_SIZE / 360.0f );
+
+	sinValue = tr.sinTable[ index & FUNCTABLE_MASK ];
+	cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ];
+
+	tmi.matrix[0][0] = cosValue;
+	tmi.matrix[1][0] = -sinValue;
+	tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;
+
+	tmi.matrix[0][1] = sinValue;
+	tmi.matrix[1][1] = cosValue;
+	tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;
+
+	RB_CalcTransformTexMatrix( &tmi, matrix );
+}
+/*
+** RB_CalcSpecularAlpha
+**
+** Calculates specular coefficient and places it in the alpha channel
+*/
+vec3_t lightOrigin = { -960, 1980, 96 };		// FIXME: track dynamically
+
+void RB_CalcSpecularAlpha( unsigned char *alphas ) {
+	int			i;
+	float		*v, *normal;
+	vec3_t		viewer,  reflected;
+	float		l, d;
+	int			b;
+	vec3_t		lightDir;
+	int			numVertexes;
+
+	v = tess.xyz[0];
+	normal = tess.normal[0];
+
+	alphas += 3;
+
+	numVertexes = tess.numVertexes;
+	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) {
+		float ilength;
+
+		VectorSubtract( lightOrigin, v, lightDir );
+//		ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) );
+		VectorNormalizeFast( lightDir );
+
+		// calculate the specular color
+		d = DotProduct (normal, lightDir);
+//		d *= ilength;
+
+		// we don't optimize for the d < 0 case since this tends to
+		// cause visual artifacts such as faceted "snapping"
+		reflected[0] = normal[0]*2*d - lightDir[0];
+		reflected[1] = normal[1]*2*d - lightDir[1];
+		reflected[2] = normal[2]*2*d - lightDir[2];
+
+		VectorSubtract (backEnd.or.viewOrigin, v, viewer);
+		ilength = Q_rsqrt( DotProduct( viewer, viewer ) );
+		l = DotProduct (reflected, viewer);
+		l *= ilength;
+
+		if (l < 0) {
+			b = 0;
+		} else {
+			l = l*l;
+			l = l*l;
+			b = l * 255;
+			if (b > 255) {
+				b = 255;
+			}
+		}
+
+		*alphas = b;
+	}
+}
+
+/*
+** RB_CalcDiffuseColor
+**
+** The basic vertex lighting calc
+*/
+#if idppc_altivec
+static void RB_CalcDiffuseColor_altivec( unsigned char *colors )
+{
+	int				i;
+	float			*v, *normal;
+	trRefEntity_t	*ent;
+	int				ambientLightInt;
+	vec3_t			lightDir;
+	int				numVertexes;
+	vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff,
+                                               0x00, 0x00, 0x00, 0xff,
+                                               0x00, 0x00, 0x00, 0xff,
+                                               0x00, 0x00, 0x00, 0xff);
+	vector float ambientLightVec;
+	vector float directedLightVec;
+	vector float lightDirVec;
+	vector float normalVec0, normalVec1;
+	vector float incomingVec0, incomingVec1, incomingVec2;
+	vector float zero, jVec;
+	vector signed int jVecInt;
+	vector signed short jVecShort;
+	vector unsigned char jVecChar, normalPerm;
+	ent = backEnd.currentEntity;
+	ambientLightInt = ent->ambientLightInt;
+	// A lot of this could be simplified if we made sure
+	// entities light info was 16-byte aligned.
+	jVecChar = vec_lvsl(0, ent->ambientLight);
+	ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight);
+	jVec = vec_ld(11, (vector float *)ent->ambientLight);
+	ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar);
+
+	jVecChar = vec_lvsl(0, ent->directedLight);
+	directedLightVec = vec_ld(0,(vector float *)ent->directedLight);
+	jVec = vec_ld(11,(vector float *)ent->directedLight);
+	directedLightVec = vec_perm(directedLightVec,jVec,jVecChar);	 
+
+	jVecChar = vec_lvsl(0, ent->lightDir);
+	lightDirVec = vec_ld(0,(vector float *)ent->lightDir);
+	jVec = vec_ld(11,(vector float *)ent->lightDir);
+	lightDirVec = vec_perm(lightDirVec,jVec,jVecChar);	 
+
+	zero = (vector float)vec_splat_s8(0);
+	VectorCopy( ent->lightDir, lightDir );
+
+	v = tess.xyz[0];
+	normal = tess.normal[0];
+
+	normalPerm = vec_lvsl(0,normal);
+	numVertexes = tess.numVertexes;
+	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) {
+		normalVec0 = vec_ld(0,(vector float *)normal);
+		normalVec1 = vec_ld(11,(vector float *)normal);
+		normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm);
+		incomingVec0 = vec_madd(normalVec0, lightDirVec, zero);
+		incomingVec1 = vec_sld(incomingVec0,incomingVec0,4);
+		incomingVec2 = vec_add(incomingVec0,incomingVec1);
+		incomingVec1 = vec_sld(incomingVec1,incomingVec1,4);
+		incomingVec2 = vec_add(incomingVec2,incomingVec1);
+		incomingVec0 = vec_splat(incomingVec2,0);
+		incomingVec0 = vec_max(incomingVec0,zero);
+		normalPerm = vec_lvsl(12,normal);
+		jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec);
+		jVecInt = vec_cts(jVec,0);	// RGBx
+		jVecShort = vec_pack(jVecInt,jVecInt);		// RGBxRGBx
+		jVecChar = vec_packsu(jVecShort,jVecShort);	// RGBxRGBxRGBxRGBx
+		jVecChar = vec_sel(jVecChar,vSel,vSel);		// RGBARGBARGBARGBA replace alpha with 255
+		vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]);	// store color
+	}
+}
+#endif
+
+static void RB_CalcDiffuseColor_scalar( unsigned char *colors )
+{
+	int				i, j;
+	float			*v, *normal;
+	float			incoming;
+	trRefEntity_t	*ent;
+	int				ambientLightInt;
+	vec3_t			ambientLight;
+	vec3_t			lightDir;
+	vec3_t			directedLight;
+	int				numVertexes;
+	ent = backEnd.currentEntity;
+	ambientLightInt = ent->ambientLightInt;
+	VectorCopy( ent->ambientLight, ambientLight );
+	VectorCopy( ent->directedLight, directedLight );
+	VectorCopy( ent->lightDir, lightDir );
+
+	v = tess.xyz[0];
+	normal = tess.normal[0];
+
+	numVertexes = tess.numVertexes;
+	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) {
+		incoming = DotProduct (normal, lightDir);
+		if ( incoming <= 0 ) {
+			*(int *)&colors[i*4] = ambientLightInt;
+			continue;
+		} 
+		j = ri.ftol(ambientLight[0] + incoming * directedLight[0]);
+		if ( j > 255 ) {
+			j = 255;
+		}
+		colors[i*4+0] = j;
+
+		j = ri.ftol(ambientLight[1] + incoming * directedLight[1]);
+		if ( j > 255 ) {
+			j = 255;
+		}
+		colors[i*4+1] = j;
+
+		j = ri.ftol(ambientLight[2] + incoming * directedLight[2]);
+		if ( j > 255 ) {
+			j = 255;
+		}
+		colors[i*4+2] = j;
+
+		colors[i*4+3] = 255;
+	}
+}
+
+void RB_CalcDiffuseColor( unsigned char *colors )
+{
+#if idppc_altivec
+	if (com_altivec->integer) {
+		// must be in a seperate function or G3 systems will crash.
+		RB_CalcDiffuseColor_altivec( colors );
+		return;
+	}
+#endif
+	RB_CalcDiffuseColor_scalar( colors );
+}
+
+
+
+
+

Added: trunk/code/rend2/tr_shader.c
===================================================================
--- trunk/code/rend2/tr_shader.c	                        (rev 0)
+++ trunk/code/rend2/tr_shader.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,3728 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+// tr_shader.c -- this file deals with the parsing and definition of shaders
+
+static char *s_shaderText;
+
+// the shader is parsed into these global variables, then copied into
+// dynamically allocated memory if it is valid.
+static	shaderStage_t	stages[MAX_SHADER_STAGES];		
+static	shader_t		shader;
+static	texModInfo_t	texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
+
+#define FILE_HASH_SIZE		1024
+static	shader_t*		hashTable[FILE_HASH_SIZE];
+
+#define MAX_SHADERTEXT_HASH		2048
+static char **shaderTextHashTable[MAX_SHADERTEXT_HASH];
+
+/*
+================
+return a hash value for the filename
+================
+*/
+#ifdef __GNUCC__
+  #warning TODO: check if long is ok here 
+#endif
+static long generateHashValue( const char *fname, const int size ) {
+	int		i;
+	long	hash;
+	char	letter;
+
+	hash = 0;
+	i = 0;
+	while (fname[i] != '\0') {
+		letter = tolower(fname[i]);
+		if (letter =='.') break;				// don't include extension
+		if (letter =='\\') letter = '/';		// damn path names
+		if (letter == PATH_SEP) letter = '/';		// damn path names
+		hash+=(long)(letter)*(i+119);
+		i++;
+	}
+	hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+	hash &= (size-1);
+	return hash;
+}
+
+void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) {
+	char		strippedName[MAX_QPATH];
+	int			hash;
+	shader_t	*sh, *sh2;
+	qhandle_t	h;
+
+	sh = R_FindShaderByName( shaderName );
+	if (sh == NULL || sh == tr.defaultShader) {
+		h = RE_RegisterShaderLightMap(shaderName, 0);
+		sh = R_GetShaderByHandle(h);
+	}
+	if (sh == NULL || sh == tr.defaultShader) {
+		ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName );
+		return;
+	}
+
+	sh2 = R_FindShaderByName( newShaderName );
+	if (sh2 == NULL || sh2 == tr.defaultShader) {
+		h = RE_RegisterShaderLightMap(newShaderName, 0);
+		sh2 = R_GetShaderByHandle(h);
+	}
+
+	if (sh2 == NULL || sh2 == tr.defaultShader) {
+		ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName );
+		return;
+	}
+
+	// remap all the shaders with the given name
+	// even tho they might have different lightmaps
+	COM_StripExtension(shaderName, strippedName, sizeof(strippedName));
+	hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+	for (sh = hashTable[hash]; sh; sh = sh->next) {
+		if (Q_stricmp(sh->name, strippedName) == 0) {
+			if (sh != sh2) {
+				sh->remappedShader = sh2;
+			} else {
+				sh->remappedShader = NULL;
+			}
+		}
+	}
+	if (timeOffset) {
+		sh2->timeOffset = atof(timeOffset);
+	}
+}
+
+/*
+===============
+ParseVector
+===============
+*/
+static qboolean ParseVector( char **text, int count, float *v ) {
+	char	*token;
+	int		i;
+
+	// FIXME: spaces are currently required after parens, should change parseext...
+	token = COM_ParseExt( text, qfalse );
+	if ( strcmp( token, "(" ) ) {
+		ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name );
+		return qfalse;
+	}
+
+	for ( i = 0 ; i < count ; i++ ) {
+		token = COM_ParseExt( text, qfalse );
+		if ( !token[0] ) {
+			ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name );
+			return qfalse;
+		}
+		v[i] = atof( token );
+	}
+
+	token = COM_ParseExt( text, qfalse );
+	if ( strcmp( token, ")" ) ) {
+		ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name );
+		return qfalse;
+	}
+
+	return qtrue;
+}
+
+
+/*
+===============
+NameToAFunc
+===============
+*/
+static unsigned NameToAFunc( const char *funcname )
+{	
+	if ( !Q_stricmp( funcname, "GT0" ) )
+	{
+		return GLS_ATEST_GT_0;
+	}
+	else if ( !Q_stricmp( funcname, "LT128" ) )
+	{
+		return GLS_ATEST_LT_80;
+	}
+	else if ( !Q_stricmp( funcname, "GE128" ) )
+	{
+		return GLS_ATEST_GE_80;
+	}
+
+	ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name );
+	return 0;
+}
+
+
+/*
+===============
+NameToSrcBlendMode
+===============
+*/
+static int NameToSrcBlendMode( const char *name )
+{
+	if ( !Q_stricmp( name, "GL_ONE" ) )
+	{
+		return GLS_SRCBLEND_ONE;
+	}
+	else if ( !Q_stricmp( name, "GL_ZERO" ) )
+	{
+		return GLS_SRCBLEND_ZERO;
+	}
+	else if ( !Q_stricmp( name, "GL_DST_COLOR" ) )
+	{
+		return GLS_SRCBLEND_DST_COLOR;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) )
+	{
+		return GLS_SRCBLEND_ONE_MINUS_DST_COLOR;
+	}
+	else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) )
+	{
+		return GLS_SRCBLEND_SRC_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) )
+	{
+		return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) )
+	{
+		return GLS_SRCBLEND_DST_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+	{
+		return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) )
+	{
+		return GLS_SRCBLEND_ALPHA_SATURATE;
+	}
+
+	ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name );
+	return GLS_SRCBLEND_ONE;
+}
+
+/*
+===============
+NameToDstBlendMode
+===============
+*/
+static int NameToDstBlendMode( const char *name )
+{
+	if ( !Q_stricmp( name, "GL_ONE" ) )
+	{
+		return GLS_DSTBLEND_ONE;
+	}
+	else if ( !Q_stricmp( name, "GL_ZERO" ) )
+	{
+		return GLS_DSTBLEND_ZERO;
+	}
+	else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) )
+	{
+		return GLS_DSTBLEND_SRC_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) )
+	{
+		return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) )
+	{
+		return GLS_DSTBLEND_DST_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+	{
+		return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA;
+	}
+	else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) )
+	{
+		return GLS_DSTBLEND_SRC_COLOR;
+	}
+	else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) )
+	{
+		return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR;
+	}
+
+	ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name );
+	return GLS_DSTBLEND_ONE;
+}
+
+/*
+===============
+NameToGenFunc
+===============
+*/
+static genFunc_t NameToGenFunc( const char *funcname )
+{
+	if ( !Q_stricmp( funcname, "sin" ) )
+	{
+		return GF_SIN;
+	}
+	else if ( !Q_stricmp( funcname, "square" ) )
+	{
+		return GF_SQUARE;
+	}
+	else if ( !Q_stricmp( funcname, "triangle" ) )
+	{
+		return GF_TRIANGLE;
+	}
+	else if ( !Q_stricmp( funcname, "sawtooth" ) )
+	{
+		return GF_SAWTOOTH;
+	}
+	else if ( !Q_stricmp( funcname, "inversesawtooth" ) )
+	{
+		return GF_INVERSE_SAWTOOTH;
+	}
+	else if ( !Q_stricmp( funcname, "noise" ) )
+	{
+		return GF_NOISE;
+	}
+
+	ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name );
+	return GF_SIN;
+}
+
+
+/*
+===================
+ParseWaveForm
+===================
+*/
+static void ParseWaveForm( char **text, waveForm_t *wave )
+{
+	char *token;
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+		return;
+	}
+	wave->func = NameToGenFunc( token );
+
+	// BASE, AMP, PHASE, FREQ
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+		return;
+	}
+	wave->base = atof( token );
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+		return;
+	}
+	wave->amplitude = atof( token );
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+		return;
+	}
+	wave->phase = atof( token );
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+		return;
+	}
+	wave->frequency = atof( token );
+}
+
+
+/*
+===================
+ParseTexMod
+===================
+*/
+static void ParseTexMod( char *_text, shaderStage_t *stage )
+{
+	const char *token;
+	char **text = &_text;
+	texModInfo_t *tmi;
+
+	if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) {
+		ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'", shader.name );
+		return;
+	}
+
+	tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods];
+	stage->bundle[0].numTexMods++;
+
+	token = COM_ParseExt( text, qfalse );
+
+	//
+	// turb
+	//
+	if ( !Q_stricmp( token, "turb" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.base = atof( token );
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.amplitude = atof( token );
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.phase = atof( token );
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.frequency = atof( token );
+
+		tmi->type = TMOD_TURBULENT;
+	}
+	//
+	// scale
+	//
+	else if ( !Q_stricmp( token, "scale" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->scale[0] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->scale[1] = atof( token );
+		tmi->type = TMOD_SCALE;
+	}
+	//
+	// scroll
+	//
+	else if ( !Q_stricmp( token, "scroll" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->scroll[0] = atof( token );
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->scroll[1] = atof( token );
+		tmi->type = TMOD_SCROLL;
+	}
+	//
+	// stretch
+	//
+	else if ( !Q_stricmp( token, "stretch" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.func = NameToGenFunc( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.base = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.amplitude = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.phase = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->wave.frequency = atof( token );
+		
+		tmi->type = TMOD_STRETCH;
+	}
+	//
+	// transform
+	//
+	else if ( !Q_stricmp( token, "transform" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->matrix[0][0] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->matrix[0][1] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->matrix[1][0] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->matrix[1][1] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->translate[0] = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->translate[1] = atof( token );
+
+		tmi->type = TMOD_TRANSFORM;
+	}
+	//
+	// rotate
+	//
+	else if ( !Q_stricmp( token, "rotate" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name );
+			return;
+		}
+		tmi->rotateSpeed = atof( token );
+		tmi->type = TMOD_ROTATE;
+	}
+	//
+	// entityTranslate
+	//
+	else if ( !Q_stricmp( token, "entityTranslate" ) )
+	{
+		tmi->type = TMOD_ENTITY_TRANSLATE;
+	}
+	else
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name );
+	}
+}
+
+
+/*
+===================
+ParseStage
+===================
+*/
+static qboolean ParseStage( shaderStage_t *stage, char **text )
+{
+	char *token;
+	int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0;
+	qboolean depthMaskExplicit = qfalse;
+
+	stage->active = qtrue;
+
+	while ( 1 )
+	{
+		token = COM_ParseExt( text, qtrue );
+		if ( !token[0] )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" );
+			return qfalse;
+		}
+
+		if ( token[0] == '}' )
+		{
+			break;
+		}
+		//
+		// map <name>
+		//
+		else if ( !Q_stricmp( token, "map" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+
+			if ( !Q_stricmp( token, "$whiteimage" ) )
+			{
+				stage->bundle[0].image[0] = tr.whiteImage;
+				continue;
+			}
+			else if ( !Q_stricmp( token, "$lightmap" ) )
+			{
+				stage->bundle[0].isLightmap = qtrue;
+				if ( shader.lightmapIndex < 0 ) {
+					stage->bundle[0].image[0] = tr.whiteImage;
+				} else {
+					stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+				}
+				continue;
+			}
+			else if ( !Q_stricmp( token, "$deluxemap" ) )
+			{
+				if (!tr.worldDeluxeMapping)
+				{
+					ri.Printf( PRINT_WARNING, "WARNING: shader '%s' wants a deluxe map in a map compiled without them\n", shader.name );
+					return qfalse;
+				}
+
+				stage->bundle[0].isLightmap = qtrue;
+				if ( shader.lightmapIndex < 0 ) {
+					stage->bundle[0].image[0] = tr.whiteImage;
+				} else {
+					stage->bundle[0].image[0] = tr.deluxemaps[shader.lightmapIndex];
+				}
+				continue;
+			}
+			else
+			{
+				imgType_t type = IMGTYPE_COLORALPHA;
+				imgFlags_t flags = IMGFLAG_NONE;
+
+				if (!shader.noMipMaps)
+					flags |= IMGFLAG_MIPMAP;
+
+				if (!shader.noPicMip)
+					flags |= IMGFLAG_PICMIP;
+
+				if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP)
+				{
+					type = IMGTYPE_NORMAL;
+					flags |= IMGFLAG_NOLIGHTSCALE;
+
+					if (stage->type == ST_NORMALPARALLAXMAP)
+						type = IMGTYPE_NORMALHEIGHT;
+				}
+				else
+				{
+					if (r_genNormalMaps->integer)
+						flags |= IMGFLAG_GENNORMALMAP;
+
+					if (r_srgb->integer)
+						flags |= IMGFLAG_SRGB;
+				}
+
+				stage->bundle[0].image[0] = R_FindImageFile( token, type, flags );
+
+				if ( !stage->bundle[0].image[0] )
+				{
+					ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+					return qfalse;
+				}
+			}
+		}
+		//
+		// clampmap <name>
+		//
+		else if ( !Q_stricmp( token, "clampmap" ) )
+		{
+			imgType_t type = IMGTYPE_COLORALPHA;
+			imgFlags_t flags = IMGFLAG_CLAMPTOEDGE | IMGFLAG_SRGB;
+
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+
+			if (!shader.noMipMaps)
+				flags |= IMGFLAG_MIPMAP;
+
+			if (!shader.noPicMip)
+				flags |= IMGFLAG_PICMIP;
+
+			if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP)
+			{
+				type = IMGTYPE_NORMAL;
+				flags |= IMGFLAG_NOLIGHTSCALE;
+
+				if (stage->type == ST_NORMALPARALLAXMAP)
+					type = IMGTYPE_NORMALHEIGHT;
+			}
+			else
+			{
+				if (r_genNormalMaps->integer)
+					flags |= IMGFLAG_GENNORMALMAP;
+
+				if (r_srgb->integer)
+					flags |= IMGFLAG_SRGB;
+			}
+
+
+			stage->bundle[0].image[0] = R_FindImageFile( token, type, flags );
+			if ( !stage->bundle[0].image[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+				return qfalse;
+			}
+		}
+		//
+		// animMap <frequency> <image1> .... <imageN>
+		//
+		else if ( !Q_stricmp( token, "animMap" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+			stage->bundle[0].imageAnimationSpeed = atof( token );
+
+			// parse up to MAX_IMAGE_ANIMATIONS animations
+			while ( 1 ) {
+				int		num;
+
+				token = COM_ParseExt( text, qfalse );
+				if ( !token[0] ) {
+					break;
+				}
+				num = stage->bundle[0].numImageAnimations;
+				if ( num < MAX_IMAGE_ANIMATIONS ) {
+					imgFlags_t flags = IMGFLAG_SRGB;
+
+					if (!shader.noMipMaps)
+						flags |= IMGFLAG_MIPMAP;
+
+					if (!shader.noPicMip)
+						flags |= IMGFLAG_PICMIP;
+
+					stage->bundle[0].image[num] = R_FindImageFile( token, IMGTYPE_COLORALPHA, flags );
+					if ( !stage->bundle[0].image[num] )
+					{
+						ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+						return qfalse;
+					}
+					stage->bundle[0].numImageAnimations++;
+				}
+			}
+		}
+		else if ( !Q_stricmp( token, "videoMap" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+			stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader));
+			if (stage->bundle[0].videoMapHandle != -1) {
+				stage->bundle[0].isVideoMap = qtrue;
+				stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle];
+			}
+		}
+		//
+		// alphafunc <func>
+		//
+		else if ( !Q_stricmp( token, "alphaFunc" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+
+			atestBits = NameToAFunc( token );
+		}
+		//
+		// depthFunc <func>
+		//
+		else if ( !Q_stricmp( token, "depthfunc" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+
+			if ( !token[0] )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name );
+				return qfalse;
+			}
+
+			if ( !Q_stricmp( token, "lequal" ) )
+			{
+				depthFuncBits = 0;
+			}
+			else if ( !Q_stricmp( token, "equal" ) )
+			{
+				depthFuncBits = GLS_DEPTHFUNC_EQUAL;
+			}
+			else
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name );
+				continue;
+			}
+		}
+		//
+		// detail
+		//
+		else if ( !Q_stricmp( token, "detail" ) )
+		{
+			stage->isDetail = qtrue;
+		}
+		//
+		// blendfunc <srcFactor> <dstFactor>
+		// or blendfunc <add|filter|blend>
+		//
+		else if ( !Q_stricmp( token, "blendfunc" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name );
+				continue;
+			}
+			// check for "simple" blends first
+			if ( !Q_stricmp( token, "add" ) ) {
+				blendSrcBits = GLS_SRCBLEND_ONE;
+				blendDstBits = GLS_DSTBLEND_ONE;
+			} else if ( !Q_stricmp( token, "filter" ) ) {
+				blendSrcBits = GLS_SRCBLEND_DST_COLOR;
+				blendDstBits = GLS_DSTBLEND_ZERO;
+			} else if ( !Q_stricmp( token, "blend" ) ) {
+				blendSrcBits = GLS_SRCBLEND_SRC_ALPHA;
+				blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+			} else {
+				// complex double blends
+				blendSrcBits = NameToSrcBlendMode( token );
+
+				token = COM_ParseExt( text, qfalse );
+				if ( token[0] == 0 )
+				{
+					ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name );
+					continue;
+				}
+				blendDstBits = NameToDstBlendMode( token );
+			}
+
+			// clear depth mask for blended surfaces
+			if ( !depthMaskExplicit )
+			{
+				depthMaskBits = 0;
+			}
+		}
+		//
+		// stage <type>
+		//
+		else if(!Q_stricmp(token, "stage"))
+		{
+			token = COM_ParseExt(text, qfalse);
+			if(token[0] == 0)
+			{
+				ri.Printf(PRINT_WARNING, "WARNING: missing parameters for stage in shader '%s'\n", shader.name);
+				continue;
+			}
+
+			if(!Q_stricmp(token, "diffuseMap"))
+			{
+				stage->type = ST_DIFFUSEMAP;
+			}
+			else if(!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap"))
+			{
+				stage->type = ST_NORMALMAP;
+			}
+			else if(!Q_stricmp(token, "normalParallaxMap") || !Q_stricmp(token, "bumpParallaxMap"))
+			{
+				if (r_parallaxMapping->integer)
+					stage->type = ST_NORMALPARALLAXMAP;
+				else
+					stage->type = ST_NORMALMAP;
+			}
+			else if(!Q_stricmp(token, "specularMap"))
+			{
+				stage->type = ST_SPECULARMAP;
+				stage->materialInfo[0] = 0.04f;
+				stage->materialInfo[1] = 256.0f;
+			}
+			else
+			{
+				ri.Printf(PRINT_WARNING, "WARNING: unknown stage parameter '%s' in shader '%s'\n", token, shader.name);
+				continue;
+			}
+		}
+		//
+		// specularReflectance <value>
+		//
+		else if (!Q_stricmp(token, "specularreflectance"))
+		{
+			token = COM_ParseExt(text, qfalse);
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular reflectance in shader '%s'\n", shader.name );
+				continue;
+			}
+			stage->materialInfo[0] = atof( token );
+		}
+		//
+		// specularExponent <value>
+		//
+		else if (!Q_stricmp(token, "specularexponent"))
+		{
+			token = COM_ParseExt(text, qfalse);
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular exponent in shader '%s'\n", shader.name );
+				continue;
+			}
+			stage->materialInfo[1] = atof( token );
+		}
+		//
+		// rgbGen
+		//
+		else if ( !Q_stricmp( token, "rgbGen" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name );
+				continue;
+			}
+
+			if ( !Q_stricmp( token, "wave" ) )
+			{
+				ParseWaveForm( text, &stage->rgbWave );
+				stage->rgbGen = CGEN_WAVEFORM;
+			}
+			else if ( !Q_stricmp( token, "const" ) )
+			{
+				vec3_t	color;
+
+				ParseVector( text, 3, color );
+				stage->constantColor[0] = 255 * color[0];
+				stage->constantColor[1] = 255 * color[1];
+				stage->constantColor[2] = 255 * color[2];
+
+				stage->rgbGen = CGEN_CONST;
+			}
+			else if ( !Q_stricmp( token, "identity" ) )
+			{
+				stage->rgbGen = CGEN_IDENTITY;
+			}
+			else if ( !Q_stricmp( token, "identityLighting" ) )
+			{
+				stage->rgbGen = CGEN_IDENTITY_LIGHTING;
+			}
+			else if ( !Q_stricmp( token, "entity" ) )
+			{
+				stage->rgbGen = CGEN_ENTITY;
+			}
+			else if ( !Q_stricmp( token, "oneMinusEntity" ) )
+			{
+				stage->rgbGen = CGEN_ONE_MINUS_ENTITY;
+			}
+			else if ( !Q_stricmp( token, "vertex" ) )
+			{
+				stage->rgbGen = CGEN_VERTEX;
+				if ( stage->alphaGen == 0 ) {
+					stage->alphaGen = AGEN_VERTEX;
+				}
+			}
+			else if ( !Q_stricmp( token, "exactVertex" ) )
+			{
+				stage->rgbGen = CGEN_EXACT_VERTEX;
+			}
+			else if ( !Q_stricmp( token, "vertexLit" ) )
+			{
+				stage->rgbGen = CGEN_VERTEX_LIT;
+				if ( stage->alphaGen == 0 ) {
+					stage->alphaGen = AGEN_VERTEX;
+				}
+			}
+			else if ( !Q_stricmp( token, "exactVertexLit" ) )
+			{
+				stage->rgbGen = CGEN_EXACT_VERTEX_LIT;
+			}
+			else if ( !Q_stricmp( token, "lightingDiffuse" ) )
+			{
+				stage->rgbGen = CGEN_LIGHTING_DIFFUSE;
+			}
+			else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+			{
+				stage->rgbGen = CGEN_ONE_MINUS_VERTEX;
+			}
+			else
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name );
+				continue;
+			}
+		}
+		//
+		// alphaGen 
+		//
+		else if ( !Q_stricmp( token, "alphaGen" ) )
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name );
+				continue;
+			}
+
+			if ( !Q_stricmp( token, "wave" ) )
+			{
+				ParseWaveForm( text, &stage->alphaWave );
+				stage->alphaGen = AGEN_WAVEFORM;
+			}
+			else if ( !Q_stricmp( token, "const" ) )
+			{
+				token = COM_ParseExt( text, qfalse );
+				stage->constantColor[3] = 255 * atof( token );
+				stage->alphaGen = AGEN_CONST;
+			}
+			else if ( !Q_stricmp( token, "identity" ) )
+			{
+				stage->alphaGen = AGEN_IDENTITY;
+			}
+			else if ( !Q_stricmp( token, "entity" ) )
+			{
+				stage->alphaGen = AGEN_ENTITY;
+			}
+			else if ( !Q_stricmp( token, "oneMinusEntity" ) )
+			{
+				stage->alphaGen = AGEN_ONE_MINUS_ENTITY;
+			}
+			else if ( !Q_stricmp( token, "vertex" ) )
+			{
+				stage->alphaGen = AGEN_VERTEX;
+			}
+			else if ( !Q_stricmp( token, "lightingSpecular" ) )
+			{
+				stage->alphaGen = AGEN_LIGHTING_SPECULAR;
+			}
+			else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+			{
+				stage->alphaGen = AGEN_ONE_MINUS_VERTEX;
+			}
+			else if ( !Q_stricmp( token, "portal" ) )
+			{
+				stage->alphaGen = AGEN_PORTAL;
+				token = COM_ParseExt( text, qfalse );
+				if ( token[0] == 0 )
+				{
+					shader.portalRange = 256;
+					ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name );
+				}
+				else
+				{
+					shader.portalRange = atof( token );
+				}
+			}
+			else if ( !Q_stricmp( token, "fresnel" ) )
+			{
+				stage->alphaGen = AGEN_FRESNEL;
+			}
+			else
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name );
+				continue;
+			}
+		}
+		//
+		// tcGen <function>
+		//
+		else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) 
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name );
+				continue;
+			}
+
+			if ( !Q_stricmp( token, "environment" ) )
+			{
+				stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED;
+			}
+			else if ( !Q_stricmp( token, "lightmap" ) )
+			{
+				stage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+			}
+			else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) )
+			{
+				stage->bundle[0].tcGen = TCGEN_TEXTURE;
+			}
+			else if ( !Q_stricmp( token, "vector" ) )
+			{
+				ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] );
+				ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] );
+
+				stage->bundle[0].tcGen = TCGEN_VECTOR;
+			}
+			else 
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name );
+			}
+		}
+		//
+		// tcMod <type> <...>
+		//
+		else if ( !Q_stricmp( token, "tcMod" ) )
+		{
+			char buffer[1024] = "";
+
+			while ( 1 )
+			{
+				token = COM_ParseExt( text, qfalse );
+				if ( token[0] == 0 )
+					break;
+				strcat( buffer, token );
+				strcat( buffer, " " );
+			}
+
+			ParseTexMod( buffer, stage );
+
+			continue;
+		}
+		//
+		// depthmask
+		//
+		else if ( !Q_stricmp( token, "depthwrite" ) )
+		{
+			depthMaskBits = GLS_DEPTHMASK_TRUE;
+			depthMaskExplicit = qtrue;
+
+			continue;
+		}
+		else
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name );
+			return qfalse;
+		}
+	}
+
+	//
+	// if cgen isn't explicitly specified, use either identity or identitylighting
+	//
+	if ( stage->rgbGen == CGEN_BAD ) {
+		if ( blendSrcBits == 0 ||
+			blendSrcBits == GLS_SRCBLEND_ONE || 
+			blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) {
+			stage->rgbGen = CGEN_IDENTITY_LIGHTING;
+		} else {
+			stage->rgbGen = CGEN_IDENTITY;
+		}
+	}
+
+
+	//
+	// implicitly assume that a GL_ONE GL_ZERO blend mask disables blending
+	//
+	if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && 
+		 ( blendDstBits == GLS_DSTBLEND_ZERO ) )
+	{
+		blendDstBits = blendSrcBits = 0;
+		depthMaskBits = GLS_DEPTHMASK_TRUE;
+	}
+
+	// decide which agens we can skip
+	if ( stage->alphaGen == AGEN_IDENTITY ) {
+		if ( stage->rgbGen == CGEN_IDENTITY
+			|| stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) {
+			stage->alphaGen = AGEN_SKIP;
+		}
+	}
+
+	//
+	// compute state bits
+	//
+	stage->stateBits = depthMaskBits | 
+		               blendSrcBits | blendDstBits | 
+					   atestBits | 
+					   depthFuncBits;
+
+	return qtrue;
+}
+
+/*
+===============
+ParseDeform
+
+deformVertexes wave <spread> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes normal <frequency> <amplitude>
+deformVertexes move <vector> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
+deformVertexes projectionShadow
+deformVertexes autoSprite
+deformVertexes autoSprite2
+deformVertexes text[0-7]
+===============
+*/
+static void ParseDeform( char **text ) {
+	char	*token;
+	deformStage_t	*ds;
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name );
+		return;
+	}
+
+	if ( shader.numDeforms == MAX_SHADER_DEFORMS ) {
+		ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name );
+		return;
+	}
+
+	ds = &shader.deforms[ shader.numDeforms ];
+	shader.numDeforms++;
+
+	if ( !Q_stricmp( token, "projectionShadow" ) ) {
+		ds->deformation = DEFORM_PROJECTION_SHADOW;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "autosprite" ) ) {
+		ds->deformation = DEFORM_AUTOSPRITE;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "autosprite2" ) ) {
+		ds->deformation = DEFORM_AUTOSPRITE2;
+		return;
+	}
+
+	if ( !Q_stricmpn( token, "text", 4 ) ) {
+		int		n;
+		
+		n = token[4] - '0';
+		if ( n < 0 || n > 7 ) {
+			n = 0;
+		}
+		ds->deformation = DEFORM_TEXT0 + n;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "bulge" ) )	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+			return;
+		}
+		ds->bulgeWidth = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+			return;
+		}
+		ds->bulgeHeight = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+			return;
+		}
+		ds->bulgeSpeed = atof( token );
+
+		ds->deformation = DEFORM_BULGE;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "wave" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+			return;
+		}
+
+		if ( atof( token ) != 0 )
+		{
+			ds->deformationSpread = 1.0f / atof( token );
+		}
+		else
+		{
+			ds->deformationSpread = 100.0f;
+			ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name );
+		}
+
+		ParseWaveForm( text, &ds->deformationWave );
+		ds->deformation = DEFORM_WAVE;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "normal" ) )
+	{
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+			return;
+		}
+		ds->deformationWave.amplitude = atof( token );
+
+		token = COM_ParseExt( text, qfalse );
+		if ( token[0] == 0 )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+			return;
+		}
+		ds->deformationWave.frequency = atof( token );
+
+		ds->deformation = DEFORM_NORMALS;
+		return;
+	}
+
+	if ( !Q_stricmp( token, "move" ) ) {
+		int		i;
+
+		for ( i = 0 ; i < 3 ; i++ ) {
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 ) {
+				ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+				return;
+			}
+			ds->moveVector[i] = atof( token );
+		}
+
+		ParseWaveForm( text, &ds->deformationWave );
+		ds->deformation = DEFORM_MOVE;
+		return;
+	}
+
+	ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name );
+}
+
+
+/*
+===============
+ParseSkyParms
+
+skyParms <outerbox> <cloudheight> <innerbox>
+===============
+*/
+static void ParseSkyParms( char **text ) {
+	char		*token;
+	static char	*suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
+	char		pathname[MAX_QPATH];
+	int			i;
+
+	// outerbox
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 ) {
+		ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+		return;
+	}
+	if ( strcmp( token, "-" ) ) {
+		for (i=0 ; i<6 ; i++) {
+			Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+				, token, suf[i] );
+			shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, IMGFLAG_SRGB | IMGFLAG_MIPMAP | IMGFLAG_PICMIP | IMGFLAG_CLAMPTOEDGE );
+
+			if ( !shader.sky.outerbox[i] ) {
+				shader.sky.outerbox[i] = tr.defaultImage;
+			}
+		}
+	}
+
+	// cloudheight
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 ) {
+		ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+		return;
+	}
+	shader.sky.cloudHeight = atof( token );
+	if ( !shader.sky.cloudHeight ) {
+		shader.sky.cloudHeight = 512;
+	}
+	R_InitSkyTexCoords( shader.sky.cloudHeight );
+
+
+	// innerbox
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 ) {
+		ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+		return;
+	}
+	if ( strcmp( token, "-" ) ) {
+		for (i=0 ; i<6 ; i++) {
+			Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+				, token, suf[i] );
+			shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, IMGFLAG_SRGB | IMGFLAG_MIPMAP | IMGFLAG_PICMIP );
+			if ( !shader.sky.innerbox[i] ) {
+				shader.sky.innerbox[i] = tr.defaultImage;
+			}
+		}
+	}
+
+	shader.isSky = qtrue;
+}
+
+
+/*
+=================
+ParseSort
+=================
+*/
+void ParseSort( char **text ) {
+	char	*token;
+
+	token = COM_ParseExt( text, qfalse );
+	if ( token[0] == 0 ) {
+		ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name );
+		return;
+	}
+
+	if ( !Q_stricmp( token, "portal" ) ) {
+		shader.sort = SS_PORTAL;
+	} else if ( !Q_stricmp( token, "sky" ) ) {
+		shader.sort = SS_ENVIRONMENT;
+	} else if ( !Q_stricmp( token, "opaque" ) ) {
+		shader.sort = SS_OPAQUE;
+	}else if ( !Q_stricmp( token, "decal" ) ) {
+		shader.sort = SS_DECAL;
+	} else if ( !Q_stricmp( token, "seeThrough" ) ) {
+		shader.sort = SS_SEE_THROUGH;
+	} else if ( !Q_stricmp( token, "banner" ) ) {
+		shader.sort = SS_BANNER;
+	} else if ( !Q_stricmp( token, "additive" ) ) {
+		shader.sort = SS_BLEND1;
+	} else if ( !Q_stricmp( token, "nearest" ) ) {
+		shader.sort = SS_NEAREST;
+	} else if ( !Q_stricmp( token, "underwater" ) ) {
+		shader.sort = SS_UNDERWATER;
+	} else {
+		shader.sort = atof( token );
+	}
+}
+
+
+
+// this table is also present in q3map
+
+typedef struct {
+	char	*name;
+	int		clearSolid, surfaceFlags, contents;
+} infoParm_t;
+
+infoParm_t	infoParms[] = {
+	// server relevant contents
+	{"water",		1,	0,	CONTENTS_WATER },
+	{"slime",		1,	0,	CONTENTS_SLIME },		// mildly damaging
+	{"lava",		1,	0,	CONTENTS_LAVA },		// very damaging
+	{"playerclip",	1,	0,	CONTENTS_PLAYERCLIP },
+	{"monsterclip",	1,	0,	CONTENTS_MONSTERCLIP },
+	{"nodrop",		1,	0,	CONTENTS_NODROP },		// don't drop items or leave bodies (death fog, lava, etc)
+	{"nonsolid",	1,	SURF_NONSOLID,	0},						// clears the solid flag
+
+	// utility relevant attributes
+	{"origin",		1,	0,	CONTENTS_ORIGIN },		// center of rotating brushes
+	{"trans",		0,	0,	CONTENTS_TRANSLUCENT },	// don't eat contained surfaces
+	{"detail",		0,	0,	CONTENTS_DETAIL },		// don't include in structural bsp
+	{"structural",	0,	0,	CONTENTS_STRUCTURAL },	// force into structural bsp even if trnas
+	{"areaportal",	1,	0,	CONTENTS_AREAPORTAL },	// divides areas
+	{"clusterportal", 1,0,  CONTENTS_CLUSTERPORTAL },	// for bots
+	{"donotenter",  1,  0,  CONTENTS_DONOTENTER },		// for bots
+
+	{"fog",			1,	0,	CONTENTS_FOG},			// carves surfaces entering
+	{"sky",			0,	SURF_SKY,		0 },		// emit light from an environment map
+	{"lightfilter",	0,	SURF_LIGHTFILTER, 0 },		// filter light going through it
+	{"alphashadow",	0,	SURF_ALPHASHADOW, 0 },		// test light on a per-pixel basis
+	{"hint",		0,	SURF_HINT,		0 },		// use as a primary splitter
+
+	// server attributes
+	{"slick",		0,	SURF_SLICK,		0 },
+	{"noimpact",	0,	SURF_NOIMPACT,	0 },		// don't make impact explosions or marks
+	{"nomarks",		0,	SURF_NOMARKS,	0 },		// don't make impact marks, but still explode
+	{"ladder",		0,	SURF_LADDER,	0 },
+	{"nodamage",	0,	SURF_NODAMAGE,	0 },
+	{"metalsteps",	0,	SURF_METALSTEPS,0 },
+	{"flesh",		0,	SURF_FLESH,		0 },
+	{"nosteps",		0,	SURF_NOSTEPS,	0 },
+
+	// drawsurf attributes
+	{"nodraw",		0,	SURF_NODRAW,	0 },	// don't generate a drawsurface (or a lightmap)
+	{"pointlight",	0,	SURF_POINTLIGHT, 0 },	// sample lighting at vertexes
+	{"nolightmap",	0,	SURF_NOLIGHTMAP,0 },	// don't generate a lightmap
+	{"nodlight",	0,	SURF_NODLIGHT, 0 },		// don't ever add dynamic lights
+	{"dust",		0,	SURF_DUST, 0}			// leave a dust trail when walking on this surface
+};
+
+
+/*
+===============
+ParseSurfaceParm
+
+surfaceparm <name>
+===============
+*/
+static void ParseSurfaceParm( char **text ) {
+	char	*token;
+	int		numInfoParms = ARRAY_LEN( infoParms );
+	int		i;
+
+	token = COM_ParseExt( text, qfalse );
+	for ( i = 0 ; i < numInfoParms ; i++ ) {
+		if ( !Q_stricmp( token, infoParms[i].name ) ) {
+			shader.surfaceFlags |= infoParms[i].surfaceFlags;
+			shader.contentFlags |= infoParms[i].contents;
+#if 0
+			if ( infoParms[i].clearSolid ) {
+				si->contents &= ~CONTENTS_SOLID;
+			}
+#endif
+			break;
+		}
+	}
+}
+
+/*
+=================
+ParseShader
+
+The current text pointer is at the explicit text definition of the
+shader.  Parse it into the global shader variable.  Later functions
+will optimize it.
+=================
+*/
+static qboolean ParseShader( char **text )
+{
+	char *token;
+	int s;
+
+	s = 0;
+
+	token = COM_ParseExt( text, qtrue );
+	if ( token[0] != '{' )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name );
+		return qfalse;
+	}
+
+	while ( 1 )
+	{
+		token = COM_ParseExt( text, qtrue );
+		if ( !token[0] )
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name );
+			return qfalse;
+		}
+
+		// end of shader definition
+		if ( token[0] == '}' )
+		{
+			break;
+		}
+		// stage definition
+		else if ( token[0] == '{' )
+		{
+			if ( s >= MAX_SHADER_STAGES ) {
+				ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s\n", shader.name );
+				return qfalse;
+			}
+
+			if ( !ParseStage( &stages[s], text ) )
+			{
+				return qfalse;
+			}
+			stages[s].active = qtrue;
+			s++;
+
+			continue;
+		}
+		// skip stuff that only the QuakeEdRadient needs
+		else if ( !Q_stricmpn( token, "qer", 3 ) ) {
+			SkipRestOfLine( text );
+			continue;
+		}
+		// sun parms
+		else if ( !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) || !Q_stricmp( token, "q3gl2_sun" ) ) {
+			float	a, b;
+			qboolean isGL2Sun = qfalse;
+
+			if (!Q_stricmp( token, "q3gl2_sun" ) && r_sunShadows->integer )
+			{
+				isGL2Sun = qtrue;
+				tr.sunShadows = qtrue;
+			}
+
+			token = COM_ParseExt( text, qfalse );
+			tr.sunLight[0] = atof( token );
+			token = COM_ParseExt( text, qfalse );
+			tr.sunLight[1] = atof( token );
+			token = COM_ParseExt( text, qfalse );
+			tr.sunLight[2] = atof( token );
+			
+			VectorNormalize( tr.sunLight );
+
+			token = COM_ParseExt( text, qfalse );
+			a = atof( token );
+			VectorScale( tr.sunLight, a, tr.sunLight);
+
+			VectorSet( tr.sunAmbient, 0.0f, 0.0f, 0.0f);
+
+			token = COM_ParseExt( text, qfalse );
+			a = atof( token );
+			a = a / 180 * M_PI;
+
+			token = COM_ParseExt( text, qfalse );
+			b = atof( token );
+			b = b / 180 * M_PI;
+
+			tr.sunDirection[0] = cos( a ) * cos( b );
+			tr.sunDirection[1] = sin( a ) * cos( b );
+			tr.sunDirection[2] = sin( b );
+
+			if (isGL2Sun)
+			{
+				token = COM_ParseExt( text, qfalse );
+				tr.mapLightScale = atof(token);
+
+				token = COM_ParseExt( text, qfalse );
+				VectorScale( tr.sunLight, atof(token), tr.sunAmbient );
+			}
+
+			SkipRestOfLine( text );
+			continue;
+		}
+		// tonemap parms
+		else if ( !Q_stricmp( token, "q3gl2_tonemap" ) ) {
+			token = COM_ParseExt( text, qfalse );
+			tr.toneMinAvgMaxLevel[0] = atof( token );
+			token = COM_ParseExt( text, qfalse );
+			tr.toneMinAvgMaxLevel[1] = atof( token );
+			token = COM_ParseExt( text, qfalse );
+			tr.toneMinAvgMaxLevel[2] = atof( token );
+
+			token = COM_ParseExt( text, qfalse );
+			tr.autoExposureMinMax[0] = atof( token );
+			token = COM_ParseExt( text, qfalse );
+			tr.autoExposureMinMax[1] = atof( token );
+
+			SkipRestOfLine( text );
+			continue;
+		}
+		else if ( !Q_stricmp( token, "deformVertexes" ) ) {
+			ParseDeform( text );
+			continue;
+		}
+		else if ( !Q_stricmp( token, "tesssize" ) ) {
+			SkipRestOfLine( text );
+			continue;
+		}
+		else if ( !Q_stricmp( token, "clampTime" ) ) {
+			token = COM_ParseExt( text, qfalse );
+			if (token[0]) {
+				shader.clampTime = atof(token);
+			}
+		}
+		// skip stuff that only the q3map needs
+		else if ( !Q_stricmpn( token, "q3map", 5 ) ) {
+			SkipRestOfLine( text );
+			continue;
+		}
+		// skip stuff that only q3map or the server needs
+		else if ( !Q_stricmp( token, "surfaceParm" ) ) {
+			ParseSurfaceParm( text );
+			continue;
+		}
+		// no mip maps
+		else if ( !Q_stricmp( token, "nomipmaps" ) )
+		{
+			shader.noMipMaps = qtrue;
+			shader.noPicMip = qtrue;
+			continue;
+		}
+		// no picmip adjustment
+		else if ( !Q_stricmp( token, "nopicmip" ) )
+		{
+			shader.noPicMip = qtrue;
+			continue;
+		}
+		// polygonOffset
+		else if ( !Q_stricmp( token, "polygonOffset" ) )
+		{
+			shader.polygonOffset = qtrue;
+			continue;
+		}
+		// entityMergable, allowing sprite surfaces from multiple entities
+		// to be merged into one batch.  This is a savings for smoke
+		// puffs and blood, but can't be used for anything where the
+		// shader calcs (not the surface function) reference the entity color or scroll
+		else if ( !Q_stricmp( token, "entityMergable" ) )
+		{
+			shader.entityMergable = qtrue;
+			continue;
+		}
+		// fogParms
+		else if ( !Q_stricmp( token, "fogParms" ) ) 
+		{
+			if ( !ParseVector( text, 3, shader.fogParms.color ) ) {
+				return qfalse;
+			}
+
+			token = COM_ParseExt( text, qfalse );
+			if ( !token[0] ) 
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name );
+				continue;
+			}
+			shader.fogParms.depthForOpaque = atof( token );
+
+			// skip any old gradient directions
+			SkipRestOfLine( text );
+			continue;
+		}
+		// portal
+		else if ( !Q_stricmp(token, "portal") )
+		{
+			shader.sort = SS_PORTAL;
+			shader.isPortal = qtrue;
+			continue;
+		}
+		// skyparms <cloudheight> <outerbox> <innerbox>
+		else if ( !Q_stricmp( token, "skyparms" ) )
+		{
+			ParseSkyParms( text );
+			continue;
+		}
+		// light <value> determines flaring in q3map, not needed here
+		else if ( !Q_stricmp(token, "light") ) 
+		{
+			token = COM_ParseExt( text, qfalse );
+			continue;
+		}
+		// cull <face>
+		else if ( !Q_stricmp( token, "cull") ) 
+		{
+			token = COM_ParseExt( text, qfalse );
+			if ( token[0] == 0 )
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name );
+				continue;
+			}
+
+			if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) )
+			{
+				shader.cullType = CT_TWO_SIDED;
+			}
+			else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) )
+			{
+				shader.cullType = CT_BACK_SIDED;
+			}
+			else
+			{
+				ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name );
+			}
+			continue;
+		}
+		// sort
+		else if ( !Q_stricmp( token, "sort" ) )
+		{
+			ParseSort( text );
+			continue;
+		}
+		else
+		{
+			ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name );
+			return qfalse;
+		}
+	}
+
+	//
+	// ignore shaders that don't have any stages, unless it is a sky or fog
+	//
+	if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) {
+		return qfalse;
+	}
+
+	shader.explicitlyDefined = qtrue;
+
+	return qtrue;
+}
+
+/*
+========================================================================================
+
+SHADER OPTIMIZATION AND FOGGING
+
+========================================================================================
+*/
+
+/*
+===================
+ComputeStageIteratorFunc
+
+See if we can use on of the simple fastpath stage functions,
+otherwise set to the generic stage function
+===================
+*/
+static void ComputeStageIteratorFunc( void )
+{
+	shader.optimalStageIteratorFunc = RB_StageIteratorGeneric;
+
+	//
+	// see if this should go into the sky path
+	//
+	if ( shader.isSky )
+	{
+		shader.optimalStageIteratorFunc = RB_StageIteratorSky;
+		return;
+	}
+}
+
+/*
+===================
+ComputeVertexAttribs
+
+Check which vertex attributes we only need, so we
+don't need to submit/copy all of them.
+===================
+*/
+static void ComputeVertexAttribs(void)
+{
+	int i, stage;
+
+	// dlights always need ATTR_NORMAL
+	shader.vertexAttribs = ATTR_POSITION | ATTR_NORMAL;
+
+	// portals always need normals, for SurfIsOffscreen()
+	if (shader.isPortal)
+	{
+		shader.vertexAttribs |= ATTR_NORMAL;
+	}
+
+	if (shader.defaultShader)
+	{
+		shader.vertexAttribs |= ATTR_TEXCOORD;
+		return;
+	}
+
+	if(shader.numDeforms)
+	{
+		for ( i = 0; i < shader.numDeforms; i++)
+		{
+			deformStage_t  *ds = &shader.deforms[i];
+
+			switch (ds->deformation)
+			{
+				case DEFORM_BULGE:
+					shader.vertexAttribs |= ATTR_NORMAL | ATTR_TEXCOORD;
+					break;
+
+				case DEFORM_AUTOSPRITE:
+					shader.vertexAttribs |= ATTR_NORMAL | ATTR_COLOR;
+					break;
+
+				case DEFORM_WAVE:
+				case DEFORM_NORMALS:
+				case DEFORM_TEXT0:
+				case DEFORM_TEXT1:
+				case DEFORM_TEXT2:
+				case DEFORM_TEXT3:
+				case DEFORM_TEXT4:
+				case DEFORM_TEXT5:
+				case DEFORM_TEXT6:
+				case DEFORM_TEXT7:
+					shader.vertexAttribs |= ATTR_NORMAL;
+					break;
+
+				default:
+				case DEFORM_NONE:
+				case DEFORM_MOVE:
+				case DEFORM_PROJECTION_SHADOW:
+				case DEFORM_AUTOSPRITE2:
+					break;
+			}
+		}
+	}
+
+	for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
+	{
+		shaderStage_t *pStage = &stages[stage];
+
+		if ( !pStage->active ) 
+		{
+			break;
+		}
+
+		if (pStage->glslShaderGroup == tr.lightallShader)
+		{
+			shader.vertexAttribs |= ATTR_NORMAL;
+
+#ifdef USE_VERT_TANGENT_SPACE
+			if (pStage->glslShaderIndex & LIGHTDEF_USE_NORMALMAP)
+			{
+				shader.vertexAttribs |= ATTR_BITANGENT | ATTR_TANGENT;
+			}
+#endif
+
+			switch (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK)
+			{
+				case LIGHTDEF_USE_LIGHTMAP:
+				case LIGHTDEF_USE_LIGHT_VERTEX:
+					shader.vertexAttribs |= ATTR_LIGHTDIRECTION;
+					break;
+				default:
+					break;
+			}
+		}
+
+		for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+		{
+			if ( pStage->bundle[i].image[0] == 0 )
+			{
+				continue;
+			}
+
+			switch(pStage->bundle[i].tcGen)
+			{
+				case TCGEN_TEXTURE:
+					shader.vertexAttribs |= ATTR_TEXCOORD;
+					break;
+				case TCGEN_LIGHTMAP:
+					shader.vertexAttribs |= ATTR_LIGHTCOORD;
+					break;
+				case TCGEN_ENVIRONMENT_MAPPED:
+					shader.vertexAttribs |= ATTR_NORMAL;
+					break;
+
+				default:
+					break;
+			}
+		}
+
+		switch(pStage->rgbGen)
+		{
+			case CGEN_EXACT_VERTEX:
+			case CGEN_VERTEX:
+			case CGEN_EXACT_VERTEX_LIT:
+			case CGEN_VERTEX_LIT:
+			case CGEN_ONE_MINUS_VERTEX:
+				shader.vertexAttribs |= ATTR_COLOR;
+				break;
+
+			case CGEN_LIGHTING_DIFFUSE:
+				shader.vertexAttribs |= ATTR_NORMAL;
+				break;
+
+			default:
+				break;
+		}
+
+		switch(pStage->alphaGen)
+		{
+			case AGEN_LIGHTING_SPECULAR:
+			case AGEN_FRESNEL:
+				shader.vertexAttribs |= ATTR_NORMAL;
+				break;
+
+			case AGEN_VERTEX:
+			case AGEN_ONE_MINUS_VERTEX:
+				shader.vertexAttribs |= ATTR_COLOR;
+				break;
+
+			default:
+				break;
+		}
+	}
+}
+
+typedef struct {
+	int		blendA;
+	int		blendB;
+
+	int		multitextureEnv;
+	int		multitextureBlend;
+} collapse_t;
+
+static collapse_t	collapse[] = {
+	{ 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,	
+		GL_MODULATE, 0 },
+
+	{ 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+		GL_MODULATE, 0 },
+
+	{ GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+		GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+	{ GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+		GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+	{ GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,
+		GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+	{ GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,
+		GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+	{ 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE,
+		GL_ADD, 0 },
+
+	{ GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE,
+		GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE },
+#if 0
+	{ 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA,
+		GL_DECAL, 0 },
+#endif
+	{ -1 }
+};
+
+/*
+================
+CollapseMultitexture
+
+Attempt to combine two stages into a single multitexture stage
+FIXME: I think modulated add + modulated add collapses incorrectly
+=================
+*/
+static qboolean CollapseMultitexture( void ) {
+	int abits, bbits;
+	int i;
+	textureBundle_t tmpBundle;
+
+	if ( !qglActiveTextureARB ) {
+		return qfalse;
+	}
+
+	// make sure both stages are active
+	if ( !stages[0].active || !stages[1].active ) {
+		return qfalse;
+	}
+
+	// on voodoo2, don't combine different tmus
+	if ( glConfig.driverType == GLDRV_VOODOO ) {
+		if ( stages[0].bundle[0].image[0]->TMU ==
+			 stages[1].bundle[0].image[0]->TMU ) {
+			return qfalse;
+		}
+	}
+
+	abits = stages[0].stateBits;
+	bbits = stages[1].stateBits;
+
+	// make sure that both stages have identical state other than blend modes
+	if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) !=
+		( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) {
+		return qfalse;
+	}
+
+	abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+	bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+
+	// search for a valid multitexture blend function
+	for ( i = 0; collapse[i].blendA != -1 ; i++ ) {
+		if ( abits == collapse[i].blendA
+			&& bbits == collapse[i].blendB ) {
+			break;
+		}
+	}
+
+	// nothing found
+	if ( collapse[i].blendA == -1 ) {
+		return qfalse;
+	}
+
+	// GL_ADD is a separate extension
+	if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) {
+		return qfalse;
+	}
+
+	// make sure waveforms have identical parameters
+	if ( ( stages[0].rgbGen != stages[1].rgbGen ) ||
+		( stages[0].alphaGen != stages[1].alphaGen ) )  {
+		return qfalse;
+	}
+
+	// an add collapse can only have identity colors
+	if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) {
+		return qfalse;
+	}
+
+	if ( stages[0].rgbGen == CGEN_WAVEFORM )
+	{
+		if ( memcmp( &stages[0].rgbWave,
+					 &stages[1].rgbWave,
+					 sizeof( stages[0].rgbWave ) ) )
+		{
+			return qfalse;
+		}
+	}
+	if ( stages[0].alphaGen == AGEN_WAVEFORM )
+	{
+		if ( memcmp( &stages[0].alphaWave,
+					 &stages[1].alphaWave,
+					 sizeof( stages[0].alphaWave ) ) )
+		{
+			return qfalse;
+		}
+	}
+
+
+	// make sure that lightmaps are in bundle 1 for 3dfx
+	if ( stages[0].bundle[0].isLightmap )
+	{
+		tmpBundle = stages[0].bundle[0];
+		stages[0].bundle[0] = stages[1].bundle[0];
+		stages[0].bundle[1] = tmpBundle;
+	}
+	else
+	{
+		stages[0].bundle[1] = stages[1].bundle[0];
+	}
+
+	// set the new blend state bits
+	shader.multitextureEnv = collapse[i].multitextureEnv;
+	stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+	stages[0].stateBits |= collapse[i].multitextureBlend;
+
+	//
+	// move down subsequent shaders
+	//
+	memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) );
+	Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) );
+
+	return qtrue;
+}
+
+static void CollapseStagesToLightall(shaderStage_t *diffuse, 
+	shaderStage_t *normal, shaderStage_t *specular, shaderStage_t *lightmap, 
+	qboolean useLightVector, qboolean useLightVertex, qboolean parallax, qboolean environment)
+{
+	int defs = 0;
+
+	//ri.Printf(PRINT_ALL, "shader %s has diffuse %s", shader.name, diffuse->bundle[0].image[0]->imgName);
+
+	// reuse diffuse, mark others inactive
+	diffuse->type = ST_GLSL;
+
+	if (lightmap)
+	{
+		//ri.Printf(PRINT_ALL, ", lightmap");
+		diffuse->bundle[TB_LIGHTMAP] = lightmap->bundle[0];
+		defs |= LIGHTDEF_USE_LIGHTMAP;
+	}
+	else if (useLightVector)
+	{
+		defs |= LIGHTDEF_USE_LIGHT_VECTOR;
+	}
+	else if (useLightVertex)
+	{
+		defs |= LIGHTDEF_USE_LIGHT_VERTEX;
+	}
+
+	if (r_deluxeMapping->integer && tr.worldDeluxeMapping && lightmap)
+	{
+		//ri.Printf(PRINT_ALL, ", deluxemap");
+		diffuse->bundle[TB_DELUXEMAP] = lightmap->bundle[0];
+		diffuse->bundle[TB_DELUXEMAP].image[0] = tr.deluxemaps[shader.lightmapIndex];
+		defs |= LIGHTDEF_USE_DELUXEMAP;
+	}
+
+	if (r_normalMapping->integer)
+	{
+		image_t *diffuseImg;
+		if (normal)
+		{
+			//ri.Printf(PRINT_ALL, ", normalmap %s", normal->bundle[0].image[0]->imgName);
+			diffuse->bundle[TB_NORMALMAP] = normal->bundle[0];
+			defs |= LIGHTDEF_USE_NORMALMAP;
+			if (parallax && r_parallaxMapping->integer)
+				defs |= LIGHTDEF_USE_PARALLAXMAP;
+		}
+		else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0]))
+		{
+			char normalName[MAX_QPATH];
+			image_t *normalImg;
+			imgFlags_t normalFlags = (diffuseImg->flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE;
+
+			COM_StripExtension(diffuseImg->imgName, normalName, MAX_QPATH);
+			Q_strcat(normalName, MAX_QPATH, "_n");
+
+			normalImg = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags);
+
+			if (normalImg)
+			{
+				diffuse->bundle[TB_NORMALMAP] = diffuse->bundle[0];
+				diffuse->bundle[TB_NORMALMAP].image[0] = normalImg;
+
+				defs |= LIGHTDEF_USE_NORMALMAP;
+				if (parallax && r_parallaxMapping->integer)
+					defs |= LIGHTDEF_USE_PARALLAXMAP;
+			}
+		}
+	}
+
+	if (r_specularMapping->integer)
+	{
+		if (specular)
+		{
+			//ri.Printf(PRINT_ALL, ", specularmap %s", specular->bundle[0].image[0]->imgName);
+			diffuse->bundle[TB_SPECULARMAP] = specular->bundle[0];
+			diffuse->materialInfo[0] = specular->materialInfo[0];
+			diffuse->materialInfo[1] = specular->materialInfo[1];
+			defs |= LIGHTDEF_USE_SPECULARMAP;
+		}
+	}
+
+	if (environment)
+	{
+		defs |= LIGHTDEF_TCGEN_ENVIRONMENT;
+	}
+
+	//ri.Printf(PRINT_ALL, ".\n");
+
+	diffuse->glslShaderGroup = tr.lightallShader;
+	diffuse->glslShaderIndex = defs;
+}
+
+
+static qboolean CollapseStagesToGLSL(void)
+{
+	int i, j, numStages;
+	qboolean skip = qfalse;
+
+	// skip shaders with deforms
+	if (shader.numDeforms != 0)
+	{
+		skip = qtrue;
+	}
+
+	if (!skip)
+	{
+		// if 2+ stages and first stage is lightmap, switch them
+		// this makes it easier for the later bits to process
+		if (stages[0].active && stages[0].bundle[0].isLightmap && stages[1].active)
+		{
+			int blendBits = stages[1].stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+
+			if (blendBits == (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO)
+				|| blendBits == (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR))
+			{
+				int stateBits0 = stages[0].stateBits;
+				int stateBits1 = stages[1].stateBits;
+				shaderStage_t swapStage;
+
+				swapStage = stages[0];
+				stages[0] = stages[1];
+				stages[1] = swapStage;
+
+				stages[0].stateBits = stateBits0;
+				stages[1].stateBits = stateBits1;
+			}
+		}
+	}
+
+	if (!skip)
+	{
+		// scan for shaders that aren't supported
+		for (i = 0; i < MAX_SHADER_STAGES; i++)
+		{
+			shaderStage_t *pStage = &stages[i];
+
+			if (!pStage->active)
+				continue;
+
+			if (pStage->adjustColorsForFog)
+			{
+				skip = qtrue;
+				break;
+			}
+
+			if (pStage->bundle[0].isLightmap)
+			{
+				int blendBits = pStage->stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+				
+				if (blendBits != (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO)
+					&& blendBits != (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR))
+				{
+					skip = qtrue;
+					break;
+				}
+			}
+
+			switch(pStage->bundle[0].tcGen)
+			{
+				case TCGEN_TEXTURE:
+				case TCGEN_LIGHTMAP:
+				case TCGEN_ENVIRONMENT_MAPPED:
+					break;
+				default:
+					skip = qtrue;
+					break;
+			}
+
+			switch(pStage->alphaGen)
+			{
+				case AGEN_LIGHTING_SPECULAR:
+				case AGEN_PORTAL:
+				case AGEN_FRESNEL:
+					skip = qtrue;
+					break;
+				default:
+					break;
+			}
+		}
+	}
+
+	if (!skip)
+	{
+		for (i = 0; i < MAX_SHADER_STAGES; i++)
+		{
+			shaderStage_t *pStage = &stages[i];
+			shaderStage_t *diffuse, *normal, *specular, *lightmap;
+			qboolean parallax, environment, diffuselit, vertexlit;
+
+			if (!pStage->active)
+				continue;
+
+			// skip normal and specular maps
+			if (pStage->type != ST_COLORMAP)
+				continue;
+
+			// skip lightmaps
+			if (pStage->bundle[0].isLightmap)
+				continue;
+
+			diffuse  = pStage;
+			normal   = NULL;
+			parallax = qfalse;
+			specular = NULL;
+			lightmap = NULL;
+
+			// we have a diffuse map, find matching normal, specular, and lightmap
+			for (j = i + 1; j < MAX_SHADER_STAGES; j++)
+			{
+				shaderStage_t *pStage2 = &stages[j];
+
+				if (!pStage2->active)
+					continue;
+
+				switch(pStage2->type)
+				{
+					case ST_NORMALMAP:
+						if (!normal)
+						{
+							normal = pStage2;
+						}
+						break;
+
+					case ST_NORMALPARALLAXMAP:
+						if (!normal)
+						{
+							normal = pStage2;
+							parallax = qtrue;
+						}
+						break;
+
+					case ST_SPECULARMAP:
+						if (!specular)
+						{
+							specular = pStage2;
+						}
+						break;
+
+					case ST_COLORMAP:
+						if (pStage2->bundle[0].isLightmap)
+						{
+							lightmap = pStage2;
+						}
+						break;
+
+					default:
+						break;
+				}
+			}
+
+			environment = qfalse;
+			if (diffuse->bundle[0].tcGen == TCGEN_ENVIRONMENT_MAPPED)
+			{
+				environment = qtrue;
+			}
+
+			diffuselit = qfalse;
+			if (diffuse->rgbGen == CGEN_LIGHTING_DIFFUSE)
+			{
+				diffuselit = qtrue;
+			}
+
+			vertexlit = qfalse;
+			if (diffuse->rgbGen == CGEN_VERTEX_LIT || diffuse->rgbGen == CGEN_EXACT_VERTEX_LIT)
+			{
+				vertexlit = qtrue;
+			}
+
+			CollapseStagesToLightall(diffuse, normal, specular, lightmap, diffuselit, vertexlit, parallax, environment);
+		}
+
+		// deactivate lightmap stages
+		for (i = 0; i < MAX_SHADER_STAGES; i++)
+		{
+			shaderStage_t *pStage = &stages[i];
+
+			if (!pStage->active)
+				continue;
+
+			if (pStage->bundle[0].isLightmap)
+			{
+				pStage->active = qfalse;
+			}
+		}
+	}
+
+	// deactivate normal and specular stages
+	for (i = 0; i < MAX_SHADER_STAGES; i++)
+	{
+		shaderStage_t *pStage = &stages[i];
+
+		if (!pStage->active)
+			continue;
+
+		if (pStage->type == ST_NORMALMAP)
+		{
+			pStage->active = qfalse;
+		}
+
+		if (pStage->type == ST_NORMALPARALLAXMAP)
+		{
+			pStage->active = qfalse;
+		}
+
+		if (pStage->type == ST_SPECULARMAP)
+		{
+			pStage->active = qfalse;
+		}			
+	}
+
+	// remove inactive stages
+	numStages = 0;
+	for (i = 0; i < MAX_SHADER_STAGES; i++)
+	{
+		if (!stages[i].active)
+			continue;
+
+		if (i == numStages)
+		{
+			numStages++;
+			continue;
+		}
+
+		stages[numStages] = stages[i];
+		stages[i].active = qfalse;
+		numStages++;
+	}
+
+	if (numStages == i && i >= 2 && CollapseMultitexture())
+		numStages--;
+
+	return numStages;
+}
+
+/*
+=============
+
+FixRenderCommandList
+https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
+Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated
+but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces
+to be rendered with bad shaders. To fix this, need to go through all render commands and fix
+sortedIndex.
+==============
+*/
+static void FixRenderCommandList( int newShader ) {
+	renderCommandList_t	*cmdList = &backEndData[tr.smpFrame]->commands;
+
+	if( cmdList ) {
+		const void *curCmd = cmdList->cmds;
+
+		while ( 1 ) {
+			curCmd = PADP(curCmd, sizeof(void *));
+
+			switch ( *(const int *)curCmd ) {
+			case RC_SET_COLOR:
+				{
+				const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd;
+				curCmd = (const void *)(sc_cmd + 1);
+				break;
+				}
+			case RC_STRETCH_PIC:
+				{
+				const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd;
+				curCmd = (const void *)(sp_cmd + 1);
+				break;
+				}
+			case RC_DRAW_SURFS:
+				{
+				int i;
+				drawSurf_t	*drawSurf;
+				shader_t	*shader;
+				int			fogNum;
+				int			entityNum;
+				int			dlightMap;
+				int         pshadowMap;
+				int			sortedIndex;
+				const drawSurfsCommand_t *ds_cmd =  (const drawSurfsCommand_t *)curCmd;
+
+				for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) {
+					R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap, &pshadowMap );
+                    sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1));
+					if( sortedIndex >= newShader ) {
+						sortedIndex++;
+						drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | ( (int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap;
+					}
+				}
+				curCmd = (const void *)(ds_cmd + 1);
+				break;
+				}
+			case RC_DRAW_BUFFER:
+				{
+				const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd;
+				curCmd = (const void *)(db_cmd + 1);
+				break;
+				}
+			case RC_SWAP_BUFFERS:
+				{
+				const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd;
+				curCmd = (const void *)(sb_cmd + 1);
+				break;
+				}
+			case RC_END_OF_LIST:
+			default:
+				return;
+			}
+		}
+	}
+}
+
+/*
+==============
+SortNewShader
+
+Positions the most recently created shader in the tr.sortedShaders[]
+array so that the shader->sort key is sorted reletive to the other
+shaders.
+
+Sets shader->sortedIndex
+==============
+*/
+static void SortNewShader( void ) {
+	int		i;
+	float	sort;
+	shader_t	*newShader;
+
+	newShader = tr.shaders[ tr.numShaders - 1 ];
+	sort = newShader->sort;
+
+	for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) {
+		if ( tr.sortedShaders[ i ]->sort <= sort ) {
+			break;
+		}
+		tr.sortedShaders[i+1] = tr.sortedShaders[i];
+		tr.sortedShaders[i+1]->sortedIndex++;
+	}
+
+	// Arnout: fix rendercommandlist
+	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
+	FixRenderCommandList( i+1 );
+
+	newShader->sortedIndex = i+1;
+	tr.sortedShaders[i+1] = newShader;
+}
+
+
+/*
+====================
+GeneratePermanentShader
+====================
+*/
+static shader_t *GeneratePermanentShader( void ) {
+	shader_t	*newShader;
+	int			i, b;
+	int			size, hash;
+
+	if ( tr.numShaders == MAX_SHADERS ) {
+		ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n");
+		return tr.defaultShader;
+	}
+
+	newShader = ri.Hunk_Alloc( sizeof( shader_t ), h_low );
+
+	*newShader = shader;
+
+	if ( shader.sort <= SS_OPAQUE ) {
+		newShader->fogPass = FP_EQUAL;
+	} else if ( shader.contentFlags & CONTENTS_FOG ) {
+		newShader->fogPass = FP_LE;
+	}
+
+	tr.shaders[ tr.numShaders ] = newShader;
+	newShader->index = tr.numShaders;
+	
+	tr.sortedShaders[ tr.numShaders ] = newShader;
+	newShader->sortedIndex = tr.numShaders;
+
+	tr.numShaders++;
+
+	for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) {
+		if ( !stages[i].active ) {
+			break;
+		}
+		newShader->stages[i] = ri.Hunk_Alloc( sizeof( stages[i] ), h_low );
+		*newShader->stages[i] = stages[i];
+
+		for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) {
+			size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t );
+			newShader->stages[i]->bundle[b].texMods = ri.Hunk_Alloc( size, h_low );
+			Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size );
+		}
+	}
+
+	SortNewShader();
+
+	hash = generateHashValue(newShader->name, FILE_HASH_SIZE);
+	newShader->next = hashTable[hash];
+	hashTable[hash] = newShader;
+
+	return newShader;
+}
+
+/*
+=================
+VertexLightingCollapse
+
+If vertex lighting is enabled, only render a single
+pass, trying to guess which is the correct one to best aproximate
+what it is supposed to look like.
+=================
+*/
+static void VertexLightingCollapse( void ) {
+	int		stage;
+	shaderStage_t	*bestStage;
+	int		bestImageRank;
+	int		rank;
+
+	// if we aren't opaque, just use the first pass
+	if ( shader.sort == SS_OPAQUE ) {
+
+		// pick the best texture for the single pass
+		bestStage = &stages[0];
+		bestImageRank = -999999;
+
+		for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) {
+			shaderStage_t *pStage = &stages[stage];
+
+			if ( !pStage->active ) {
+				break;
+			}
+			rank = 0;
+
+			if ( pStage->bundle[0].isLightmap ) {
+				rank -= 100;
+			}
+			if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) {
+				rank -= 5;
+			}
+			if ( pStage->bundle[0].numTexMods ) {
+				rank -= 5;
+			}
+			if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) {
+				rank -= 3;
+			}
+
+			if ( rank > bestImageRank  ) {
+				bestImageRank = rank;
+				bestStage = pStage;
+			}
+		}
+
+		stages[0].bundle[0] = bestStage->bundle[0];
+		stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+		stages[0].stateBits |= GLS_DEPTHMASK_TRUE;
+		if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+			stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+		} else {
+			stages[0].rgbGen = CGEN_EXACT_VERTEX;
+		}
+		stages[0].alphaGen = AGEN_SKIP;		
+	} else {
+		// don't use a lightmap (tesla coils)
+		if ( stages[0].bundle[0].isLightmap ) {
+			stages[0] = stages[1];
+		}
+
+		// if we were in a cross-fade cgen, hack it to normal
+		if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) {
+			stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+		}
+		if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH )
+			&& ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) {
+			stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+		}
+		if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH )
+			&& ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) {
+			stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+		}
+	}
+
+	for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) {
+		shaderStage_t *pStage = &stages[stage];
+
+		if ( !pStage->active ) {
+			break;
+		}
+
+		Com_Memset( pStage, 0, sizeof( *pStage ) );
+	}
+}
+
+/*
+=========================
+FinishShader
+
+Returns a freshly allocated shader with all the needed info
+from the current global working shader
+=========================
+*/
+static shader_t *FinishShader( void ) {
+	int stage;
+	qboolean		hasLightmapStage;
+	qboolean		vertexLightmap;
+
+	hasLightmapStage = qfalse;
+	vertexLightmap = qfalse;
+
+	//
+	// set sky stuff appropriate
+	//
+	if ( shader.isSky ) {
+		shader.sort = SS_ENVIRONMENT;
+	}
+
+	//
+	// set polygon offset
+	//
+	if ( shader.polygonOffset && !shader.sort ) {
+		shader.sort = SS_DECAL;
+	}
+
+	//
+	// set appropriate stage information
+	//
+	for ( stage = 0; stage < MAX_SHADER_STAGES; ) {
+		shaderStage_t *pStage = &stages[stage];
+
+		if ( !pStage->active ) {
+			break;
+		}
+
+    // check for a missing texture
+		if ( !pStage->bundle[0].image[0] ) {
+			ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name );
+			pStage->active = qfalse;
+			stage++;
+			continue;
+		}
+
+		//
+		// ditch this stage if it's detail and detail textures are disabled
+		//
+		if ( pStage->isDetail && !r_detailTextures->integer )
+		{
+			int index;
+			
+			for(index = stage + 1; index < MAX_SHADER_STAGES; index++)
+			{
+				if(!stages[index].active)
+					break;
+			}
+			
+			if(index < MAX_SHADER_STAGES)
+				memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage));
+			else
+			{
+				if(stage + 1 < MAX_SHADER_STAGES)
+					memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1));
+				
+				Com_Memset(&stages[index - 1], 0, sizeof(*stages));
+			}
+			
+			continue;
+		}
+
+		//
+		// default texture coordinate generation
+		//
+		if ( pStage->bundle[0].isLightmap ) {
+			if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+				pStage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+			}
+			hasLightmapStage = qtrue;
+		} else {
+			if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+				pStage->bundle[0].tcGen = TCGEN_TEXTURE;
+			}
+		}
+
+
+    // not a true lightmap but we want to leave existing 
+    // behaviour in place and not print out a warning
+    //if (pStage->rgbGen == CGEN_VERTEX) {
+    //  vertexLightmap = qtrue;
+    //}
+
+
+
+		//
+		// determine sort order and fog color adjustment
+		//
+		if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) &&
+			 ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) {
+			int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS;
+			int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS;
+
+			// fog color adjustment only works for blend modes that have a contribution
+			// that aproaches 0 as the modulate values aproach 0 --
+			// GL_ONE, GL_ONE
+			// GL_ZERO, GL_ONE_MINUS_SRC_COLOR
+			// GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
+
+			// modulate, additive
+			if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) ||
+				( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) {
+				pStage->adjustColorsForFog = ACFF_MODULATE_RGB;
+			}
+			// strict blend
+			else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) )
+			{
+				pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA;
+			}
+			// premultiplied alpha
+			else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) )
+			{
+				pStage->adjustColorsForFog = ACFF_MODULATE_RGBA;
+			} else {
+				// we can't adjust this one correctly, so it won't be exactly correct in fog
+			}
+
+			// don't screw with sort order if this is a portal or environment
+			if ( !shader.sort ) {
+				// see through item, like a grill or grate
+				if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) {
+					shader.sort = SS_SEE_THROUGH;
+				} else {
+					shader.sort = SS_BLEND0;
+				}
+			}
+		}
+		
+		stage++;
+	}
+
+	// there are times when you will need to manually apply a sort to
+	// opaque alpha tested shaders that have later blend passes
+	if ( !shader.sort ) {
+		shader.sort = SS_OPAQUE;
+	}
+
+	//
+	// if we are in r_vertexLight mode, never use a lightmap texture
+	//
+	if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) {
+		VertexLightingCollapse();
+		stage = 1;
+		hasLightmapStage = qfalse;
+	}
+
+	//
+	// look for multitexture potential
+	//
+	stage = CollapseStagesToGLSL();
+
+	if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) {
+		if (vertexLightmap) {
+			ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name );
+		} else {
+			ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name );
+			// Don't set this, it will just add duplicate shaders to the hash
+  			//shader.lightmapIndex = LIGHTMAP_NONE;
+		}
+	}
+
+
+	//
+	// compute number of passes
+	//
+	shader.numUnfoggedPasses = stage;
+
+	// fogonly shaders don't have any normal passes
+	if (stage == 0 && !shader.isSky)
+		shader.sort = SS_FOG;
+
+	// determine which stage iterator function is appropriate
+	ComputeStageIteratorFunc();
+
+	// determine which vertex attributes this shader needs
+	ComputeVertexAttribs();
+
+	return GeneratePermanentShader();
+}
+
+//========================================================================================
+
+/*
+====================
+FindShaderInShaderText
+
+Scans the combined text description of all the shader files for
+the given shader name.
+
+return NULL if not found
+
+If found, it will return a valid shader
+=====================
+*/
+static char *FindShaderInShaderText( const char *shadername ) {
+
+	char *token, *p;
+
+	int i, hash;
+
+	hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH);
+
+	if(shaderTextHashTable[hash])
+	{
+		for (i = 0; shaderTextHashTable[hash][i]; i++)
+		{
+			p = shaderTextHashTable[hash][i];
+			token = COM_ParseExt(&p, qtrue);
+		
+			if(!Q_stricmp(token, shadername))
+				return p;
+		}
+	}
+
+	p = s_shaderText;
+
+	if ( !p ) {
+		return NULL;
+	}
+
+	// look for label
+	while ( 1 ) {
+		token = COM_ParseExt( &p, qtrue );
+		if ( token[0] == 0 ) {
+			break;
+		}
+
+		if ( !Q_stricmp( token, shadername ) ) {
+			return p;
+		}
+		else {
+			// skip the definition
+			SkipBracedSection( &p );
+		}
+	}
+
+	return NULL;
+}
+
+
+/*
+==================
+R_FindShaderByName
+
+Will always return a valid shader, but it might be the
+default shader if the real one can't be found.
+==================
+*/
+shader_t *R_FindShaderByName( const char *name ) {
+	char		strippedName[MAX_QPATH];
+	int			hash;
+	shader_t	*sh;
+
+	if ( (name==NULL) || (name[0] == 0) ) {
+		return tr.defaultShader;
+	}
+
+	COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+	hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+	//
+	// see if the shader is already loaded
+	//
+	for (sh=hashTable[hash]; sh; sh=sh->next) {
+		// NOTE: if there was no shader or image available with the name strippedName
+		// then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+		// have to check all default shaders otherwise for every call to R_FindShader
+		// with that same strippedName a new default shader is created.
+		if (Q_stricmp(sh->name, strippedName) == 0) {
+			// match found
+			return sh;
+		}
+	}
+
+	return tr.defaultShader;
+}
+
+
+/*
+===============
+R_FindShader
+
+Will always return a valid shader, but it might be the
+default shader if the real one can't be found.
+
+In the interest of not requiring an explicit shader text entry to
+be defined for every single image used in the game, three default
+shader behaviors can be auto-created for any image:
+
+If lightmapIndex == LIGHTMAP_NONE, then the image will have
+dynamic diffuse lighting applied to it, as apropriate for most
+entity skin surfaces.
+
+If lightmapIndex == LIGHTMAP_2D, then the image will be used
+for 2D rendering unless an explicit shader is found
+
+If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use
+the vertex rgba modulate values, as apropriate for misc_model
+pre-lit surfaces.
+
+Other lightmapIndex values will have a lightmap stage created
+and src*dest blending applied with the texture, as apropriate for
+most world construction surfaces.
+
+===============
+*/
+shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) {
+	char		strippedName[MAX_QPATH];
+	int			i, hash;
+	char		*shaderText;
+	image_t		*image;
+	shader_t	*sh;
+
+	if ( name[0] == 0 ) {
+		return tr.defaultShader;
+	}
+
+	// use (fullbright) vertex lighting if the bsp file doesn't have
+	// lightmaps
+	if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) {
+		lightmapIndex = LIGHTMAP_BY_VERTEX;
+	} else if ( lightmapIndex < LIGHTMAP_2D ) {
+		// negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex])
+		ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex  );
+		lightmapIndex = LIGHTMAP_BY_VERTEX;
+	}
+
+	COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+	hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+	//
+	// see if the shader is already loaded
+	//
+	for (sh = hashTable[hash]; sh; sh = sh->next) {
+		// NOTE: if there was no shader or image available with the name strippedName
+		// then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+		// have to check all default shaders otherwise for every call to R_FindShader
+		// with that same strippedName a new default shader is created.
+		if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+		     !Q_stricmp(sh->name, strippedName)) {
+			// match found
+			return sh;
+		}
+	}
+
+	// make sure the render thread is stopped, because we are probably
+	// going to have to upload an image
+	if (r_smp->integer) {
+		R_SyncRenderThread();
+	}
+
+	// clear the global shader
+	Com_Memset( &shader, 0, sizeof( shader ) );
+	Com_Memset( &stages, 0, sizeof( stages ) );
+	Q_strncpyz(shader.name, strippedName, sizeof(shader.name));
+	shader.lightmapIndex = lightmapIndex;
+	for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+		stages[i].bundle[0].texMods = texMods[i];
+	}
+
+	//
+	// attempt to define shader from an explicit parameter file
+	//
+	shaderText = FindShaderInShaderText( strippedName );
+	if ( shaderText ) {
+		// enable this when building a pak file to get a global list
+		// of all explicit shaders
+		if ( r_printShaders->integer ) {
+			ri.Printf( PRINT_ALL, "*SHADER* %s\n", name );
+		}
+
+		if ( !ParseShader( &shaderText ) ) {
+			// had errors, so use default shader
+			shader.defaultShader = qtrue;
+		}
+		sh = FinishShader();
+		return sh;
+	}
+
+
+	//
+	// if not defined in the in-memory shader descriptions,
+	// look for a single supported image file
+	//
+	{
+		imgFlags_t flags;
+
+		flags = IMGFLAG_NONE;
+
+		if (r_srgb->integer)
+			flags |= IMGFLAG_SRGB;
+
+		if (mipRawImage)
+		{
+			flags |= IMGFLAG_MIPMAP | IMGFLAG_PICMIP;
+
+			if (r_genNormalMaps->integer)
+				flags |= IMGFLAG_GENNORMALMAP;
+		}
+		else
+		{
+			flags |= IMGFLAG_CLAMPTOEDGE;
+		}
+
+		image = R_FindImageFile( name, IMGTYPE_COLORALPHA, flags );
+		if ( !image ) {
+			ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name );
+			shader.defaultShader = qtrue;
+			return FinishShader();
+		}
+	}
+
+	//
+	// create the default shading commands
+	//
+	if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+		// dynamic colors at vertexes
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+		stages[0].stateBits = GLS_DEFAULT;
+	} else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+		// explicit colors at vertexes
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_EXACT_VERTEX;
+		stages[0].alphaGen = AGEN_SKIP;
+		stages[0].stateBits = GLS_DEFAULT;
+	} else if ( shader.lightmapIndex == LIGHTMAP_2D ) {
+		// GUI elements
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_VERTEX;
+		stages[0].alphaGen = AGEN_VERTEX;
+		stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
+			  GLS_SRCBLEND_SRC_ALPHA |
+			  GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+	} else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+		// fullbright level
+		stages[0].bundle[0].image[0] = tr.whiteImage;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+		stages[0].stateBits = GLS_DEFAULT;
+
+		stages[1].bundle[0].image[0] = image;
+		stages[1].active = qtrue;
+		stages[1].rgbGen = CGEN_IDENTITY;
+		stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+	} else {
+		// two pass lightmap
+		stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+		stages[0].bundle[0].isLightmap = qtrue;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_IDENTITY;	// lightmaps are scaled on creation
+													// for identitylight
+		stages[0].stateBits = GLS_DEFAULT;
+
+		stages[1].bundle[0].image[0] = image;
+		stages[1].active = qtrue;
+		stages[1].rgbGen = CGEN_IDENTITY;
+		stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+	}
+
+	return FinishShader();
+}
+
+
+qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage) {
+	int			i, hash;
+	shader_t	*sh;
+
+	hash = generateHashValue(name, FILE_HASH_SIZE);
+
+	// probably not necessary since this function
+	// only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D
+	// but better safe than sorry.
+	if ( lightmapIndex >= tr.numLightmaps ) {
+		lightmapIndex = LIGHTMAP_WHITEIMAGE;
+	}
+
+	//
+	// see if the shader is already loaded
+	//
+	for (sh=hashTable[hash]; sh; sh=sh->next) {
+		// NOTE: if there was no shader or image available with the name strippedName
+		// then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+		// have to check all default shaders otherwise for every call to R_FindShader
+		// with that same strippedName a new default shader is created.
+		if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+			// index by name
+			!Q_stricmp(sh->name, name)) {
+			// match found
+			return sh->index;
+		}
+	}
+
+	// make sure the render thread is stopped, because we are probably
+	// going to have to upload an image
+	if (r_smp->integer) {
+		R_SyncRenderThread();
+	}
+
+	// clear the global shader
+	Com_Memset( &shader, 0, sizeof( shader ) );
+	Com_Memset( &stages, 0, sizeof( stages ) );
+	Q_strncpyz(shader.name, name, sizeof(shader.name));
+	shader.lightmapIndex = lightmapIndex;
+	for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+		stages[i].bundle[0].texMods = texMods[i];
+	}
+
+	//
+	// create the default shading commands
+	//
+	if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+		// dynamic colors at vertexes
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+		stages[0].stateBits = GLS_DEFAULT;
+	} else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+		// explicit colors at vertexes
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_EXACT_VERTEX;
+		stages[0].alphaGen = AGEN_SKIP;
+		stages[0].stateBits = GLS_DEFAULT;
+	} else if ( shader.lightmapIndex == LIGHTMAP_2D ) {
+		// GUI elements
+		stages[0].bundle[0].image[0] = image;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_VERTEX;
+		stages[0].alphaGen = AGEN_VERTEX;
+		stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
+			  GLS_SRCBLEND_SRC_ALPHA |
+			  GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+	} else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+		// fullbright level
+		stages[0].bundle[0].image[0] = tr.whiteImage;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+		stages[0].stateBits = GLS_DEFAULT;
+
+		stages[1].bundle[0].image[0] = image;
+		stages[1].active = qtrue;
+		stages[1].rgbGen = CGEN_IDENTITY;
+		stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+	} else {
+		// two pass lightmap
+		stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+		stages[0].bundle[0].isLightmap = qtrue;
+		stages[0].active = qtrue;
+		stages[0].rgbGen = CGEN_IDENTITY;	// lightmaps are scaled on creation
+													// for identitylight
+		stages[0].stateBits = GLS_DEFAULT;
+
+		stages[1].bundle[0].image[0] = image;
+		stages[1].active = qtrue;
+		stages[1].rgbGen = CGEN_IDENTITY;
+		stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+	}
+
+	sh = FinishShader();
+  return sh->index; 
+}
+
+
+/* 
+====================
+RE_RegisterShader
+
+This is the exported shader entry point for the rest of the system
+It will always return an index that will be valid.
+
+This should really only be used for explicit shaders, because there is no
+way to ask for different implicit lighting modes (vertex, lightmap, etc)
+====================
+*/
+qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) {
+	shader_t	*sh;
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+	sh = R_FindShader( name, lightmapIndex, qtrue );
+
+	// we want to return 0 if the shader failed to
+	// load for some reason, but R_FindShader should
+	// still keep a name allocated for it, so if
+	// something calls RE_RegisterShader again with
+	// the same name, we don't try looking for it again
+	if ( sh->defaultShader ) {
+		return 0;
+	}
+
+	return sh->index;
+}
+
+
+/* 
+====================
+RE_RegisterShader
+
+This is the exported shader entry point for the rest of the system
+It will always return an index that will be valid.
+
+This should really only be used for explicit shaders, because there is no
+way to ask for different implicit lighting modes (vertex, lightmap, etc)
+====================
+*/
+qhandle_t RE_RegisterShader( const char *name ) {
+	shader_t	*sh;
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+	sh = R_FindShader( name, LIGHTMAP_2D, qtrue );
+
+	// we want to return 0 if the shader failed to
+	// load for some reason, but R_FindShader should
+	// still keep a name allocated for it, so if
+	// something calls RE_RegisterShader again with
+	// the same name, we don't try looking for it again
+	if ( sh->defaultShader ) {
+		return 0;
+	}
+
+	return sh->index;
+}
+
+
+/*
+====================
+RE_RegisterShaderNoMip
+
+For menu graphics that should never be picmiped
+====================
+*/
+qhandle_t RE_RegisterShaderNoMip( const char *name ) {
+	shader_t	*sh;
+
+	if ( strlen( name ) >= MAX_QPATH ) {
+		ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+		return 0;
+	}
+
+	sh = R_FindShader( name, LIGHTMAP_2D, qfalse );
+
+	// we want to return 0 if the shader failed to
+	// load for some reason, but R_FindShader should
+	// still keep a name allocated for it, so if
+	// something calls RE_RegisterShader again with
+	// the same name, we don't try looking for it again
+	if ( sh->defaultShader ) {
+		return 0;
+	}
+
+	return sh->index;
+}
+
+/*
+====================
+R_GetShaderByHandle
+
+When a handle is passed in by another module, this range checks
+it and returns a valid (possibly default) shader_t to be used internally.
+====================
+*/
+shader_t *R_GetShaderByHandle( qhandle_t hShader ) {
+	if ( hShader < 0 ) {
+	  ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader );
+		return tr.defaultShader;
+	}
+	if ( hShader >= tr.numShaders ) {
+		ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader );
+		return tr.defaultShader;
+	}
+	return tr.shaders[hShader];
+}
+
+/*
+===============
+R_ShaderList_f
+
+Dump information on all valid shaders to the console
+A second parameter will cause it to print in sorted order
+===============
+*/
+void	R_ShaderList_f (void) {
+	int			i;
+	int			count;
+	shader_t	*shader;
+
+	ri.Printf (PRINT_ALL, "-----------------------\n");
+
+	count = 0;
+	for ( i = 0 ; i < tr.numShaders ; i++ ) {
+		if ( ri.Cmd_Argc() > 1 ) {
+			shader = tr.sortedShaders[i];
+		} else {
+			shader = tr.shaders[i];
+		}
+
+		ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses );
+
+		if (shader->lightmapIndex >= 0 ) {
+			ri.Printf (PRINT_ALL, "L ");
+		} else {
+			ri.Printf (PRINT_ALL, "  ");
+		}
+		if ( shader->multitextureEnv == GL_ADD ) {
+			ri.Printf( PRINT_ALL, "MT(a) " );
+		} else if ( shader->multitextureEnv == GL_MODULATE ) {
+			ri.Printf( PRINT_ALL, "MT(m) " );
+		} else if ( shader->multitextureEnv == GL_DECAL ) {
+			ri.Printf( PRINT_ALL, "MT(d) " );
+		} else {
+			ri.Printf( PRINT_ALL, "      " );
+		}
+		if ( shader->explicitlyDefined ) {
+			ri.Printf( PRINT_ALL, "E " );
+		} else {
+			ri.Printf( PRINT_ALL, "  " );
+		}
+
+		if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) {
+			ri.Printf( PRINT_ALL, "gen " );
+		} else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) {
+			ri.Printf( PRINT_ALL, "sky " );
+		} else {
+			ri.Printf( PRINT_ALL, "    " );
+		}
+
+		if ( shader->defaultShader ) {
+			ri.Printf (PRINT_ALL,  ": %s (DEFAULTED)\n", shader->name);
+		} else {
+			ri.Printf (PRINT_ALL,  ": %s\n", shader->name);
+		}
+		count++;
+	}
+	ri.Printf (PRINT_ALL, "%i total shaders\n", count);
+	ri.Printf (PRINT_ALL, "------------------\n");
+}
+
+/*
+====================
+ScanAndLoadShaderFiles
+
+Finds and loads all .shader files, combining them into
+a single large text block that can be scanned for shader names
+=====================
+*/
+#define	MAX_SHADER_FILES	4096
+static void ScanAndLoadShaderFiles( void )
+{
+	char **shaderFiles;
+	char *buffers[MAX_SHADER_FILES];
+	char *p;
+	int numShaderFiles;
+	int i;
+	char *oldp, *token, *hashMem, *textEnd;
+	int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
+
+	long sum = 0, summand;
+	// scan for shader files
+	shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles );
+
+	if ( !shaderFiles || !numShaderFiles )
+	{
+		ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" );
+		return;
+	}
+
+	if ( numShaderFiles > MAX_SHADER_FILES ) {
+		numShaderFiles = MAX_SHADER_FILES;
+	}
+
+	// load and parse shader files
+	for ( i = 0; i < numShaderFiles; i++ )
+	{
+		char filename[MAX_QPATH];
+
+		// look for a .mtr file first
+		{
+			char *ext;
+			Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] );
+			if ( (ext = strrchr(filename, '.')) )
+			{
+				strcpy(ext, ".mtr");
+			}
+
+			if ( ri.FS_ReadFile( filename, NULL ) <= 0 )
+			{
+				Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] );
+			}
+		}
+		
+		ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename );
+		summand = ri.FS_ReadFile( filename, (void **)&buffers[i] );
+		
+		if ( !buffers[i] )
+			ri.Error( ERR_DROP, "Couldn't load %s", filename );
+		
+		// Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders.
+		p = buffers[i];
+		while(1)
+		{
+			token = COM_ParseExt(&p, qtrue);
+			
+			if(!*token)
+				break;
+			
+			oldp = p;
+			
+			token = COM_ParseExt(&p, qtrue);
+			if(token[0] != '{' && token[1] != '\0')
+			{
+				ri.Printf(PRINT_WARNING, "WARNING: Bad shader file %s has incorrect syntax.\n", filename);
+				ri.FS_FreeFile(buffers[i]);
+				buffers[i] = NULL;
+				break;
+			}
+
+			SkipBracedSection(&oldp);
+			p = oldp;
+		}
+			
+		
+		if (buffers[i])
+			sum += summand;		
+	}
+
+	// build single large buffer
+	s_shaderText = ri.Hunk_Alloc( sum + numShaderFiles*2, h_low );
+	s_shaderText[ 0 ] = '\0';
+	textEnd = s_shaderText;
+ 
+	// free in reverse order, so the temp files are all dumped
+	for ( i = numShaderFiles - 1; i >= 0 ; i-- )
+	{
+		if ( !buffers[i] )
+			continue;
+
+		strcat( textEnd, buffers[i] );
+		strcat( textEnd, "\n" );
+		textEnd += strlen( textEnd );
+		ri.FS_FreeFile( buffers[i] );
+	}
+
+	COM_Compress( s_shaderText );
+
+	// free up memory
+	ri.FS_FreeFileList( shaderFiles );
+
+	Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
+	size = 0;
+
+	p = s_shaderText;
+	// look for shader names
+	while ( 1 ) {
+		token = COM_ParseExt( &p, qtrue );
+		if ( token[0] == 0 ) {
+			break;
+		}
+
+		hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+		shaderTextHashTableSizes[hash]++;
+		size++;
+		SkipBracedSection(&p);
+	}
+
+	size += MAX_SHADERTEXT_HASH;
+
+	hashMem = ri.Hunk_Alloc( size * sizeof(char *), h_low );
+
+	for (i = 0; i < MAX_SHADERTEXT_HASH; i++) {
+		shaderTextHashTable[i] = (char **) hashMem;
+		hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *));
+	}
+
+	Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
+
+	p = s_shaderText;
+	// look for shader names
+	while ( 1 ) {
+		oldp = p;
+		token = COM_ParseExt( &p, qtrue );
+		if ( token[0] == 0 ) {
+			break;
+		}
+
+		hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+		shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;
+
+		SkipBracedSection(&p);
+	}
+
+	return;
+
+}
+
+
+/*
+====================
+CreateInternalShaders
+====================
+*/
+static void CreateInternalShaders( void ) {
+	tr.numShaders = 0;
+
+	// init the default shader
+	Com_Memset( &shader, 0, sizeof( shader ) );
+	Com_Memset( &stages, 0, sizeof( stages ) );
+
+	Q_strncpyz( shader.name, "<default>", sizeof( shader.name ) );
+
+	shader.lightmapIndex = LIGHTMAP_NONE;
+	stages[0].bundle[0].image[0] = tr.defaultImage;
+	stages[0].active = qtrue;
+	stages[0].stateBits = GLS_DEFAULT;
+	tr.defaultShader = FinishShader();
+
+	// shadow shader is just a marker
+	Q_strncpyz( shader.name, "<stencil shadow>", sizeof( shader.name ) );
+	shader.sort = SS_STENCIL_SHADOW;
+	tr.shadowShader = FinishShader();
+}
+
+static void CreateExternalShaders( void ) {
+	tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue );
+	tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue );
+
+	// Hack to make fogging work correctly on flares. Fog colors are calculated
+	// in tr_flare.c already.
+	if(!tr.flareShader->defaultShader)
+	{
+		int index;
+		
+		for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++)
+		{
+			tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE;
+			tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE;
+		}
+	}
+
+	tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue );
+}
+
+/*
+==================
+R_InitShaders
+==================
+*/
+void R_InitShaders( void ) {
+	ri.Printf( PRINT_ALL, "Initializing Shaders\n" );
+
+	Com_Memset(hashTable, 0, sizeof(hashTable));
+
+	CreateInternalShaders();
+
+	ScanAndLoadShaderFiles();
+
+	CreateExternalShaders();
+}

Added: trunk/code/rend2/tr_shadows.c
===================================================================
--- trunk/code/rend2/tr_shadows.c	                        (rev 0)
+++ trunk/code/rend2/tr_shadows.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,343 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+
+/*
+
+  for a projection shadow:
+
+  point[x] += light vector * ( z - shadow plane )
+  point[y] +=
+  point[z] = shadow plane
+
+  1 0 light[x] / light[z]
+
+*/
+
+typedef struct {
+	int		i2;
+	int		facing;
+} edgeDef_t;
+
+#define	MAX_EDGE_DEFS	32
+
+static	edgeDef_t	edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS];
+static	int			numEdgeDefs[SHADER_MAX_VERTEXES];
+static	int			facing[SHADER_MAX_INDEXES/3];
+
+void R_AddEdgeDef( int i1, int i2, int facing ) {
+	int		c;
+
+	c = numEdgeDefs[ i1 ];
+	if ( c == MAX_EDGE_DEFS ) {
+		return;		// overflow
+	}
+	edgeDefs[ i1 ][ c ].i2 = i2;
+	edgeDefs[ i1 ][ c ].facing = facing;
+
+	numEdgeDefs[ i1 ]++;
+}
+
+void R_RenderShadowEdges( void ) {
+	int		i;
+
+#if 0
+	int		numTris;
+
+	// dumb way -- render every triangle's edges
+	numTris = tess.numIndexes / 3;
+
+	for ( i = 0 ; i < numTris ; i++ ) {
+		int		i1, i2, i3;
+
+		if ( !facing[i] ) {
+			continue;
+		}
+
+		i1 = tess.indexes[ i*3 + 0 ];
+		i2 = tess.indexes[ i*3 + 1 ];
+		i3 = tess.indexes[ i*3 + 2 ];
+
+		qglBegin( GL_TRIANGLE_STRIP );
+		qglVertex3fv( tess.xyz[ i1 ] );
+		qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] );
+		qglVertex3fv( tess.xyz[ i2 ] );
+		qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] );
+		qglVertex3fv( tess.xyz[ i3 ] );
+		qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] );
+		qglVertex3fv( tess.xyz[ i1 ] );
+		qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] );
+		qglEnd();
+	}
+#else
+	int		c, c2;
+	int		j, k;
+	int		i2;
+	int		c_edges, c_rejected;
+	int		hit[2];
+
+	// an edge is NOT a silhouette edge if its face doesn't face the light,
+	// or if it has a reverse paired edge that also faces the light.
+	// A well behaved polyhedron would have exactly two faces for each edge,
+	// but lots of models have dangling edges or overfanned edges
+	c_edges = 0;
+	c_rejected = 0;
+
+	for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+		c = numEdgeDefs[ i ];
+		for ( j = 0 ; j < c ; j++ ) {
+			if ( !edgeDefs[ i ][ j ].facing ) {
+				continue;
+			}
+
+			hit[0] = 0;
+			hit[1] = 0;
+
+			i2 = edgeDefs[ i ][ j ].i2;
+			c2 = numEdgeDefs[ i2 ];
+			for ( k = 0 ; k < c2 ; k++ ) {
+				if ( edgeDefs[ i2 ][ k ].i2 == i ) {
+					hit[ edgeDefs[ i2 ][ k ].facing ]++;
+				}
+			}
+
+			// if it doesn't share the edge with another front facing
+			// triangle, it is a sil edge
+			if ( hit[ 1 ] == 0 ) {
+				qglBegin( GL_TRIANGLE_STRIP );
+				qglVertex3fv( tess.xyz[ i ] );
+				qglVertex3fv( tess.xyz[ i + tess.numVertexes ] );
+				qglVertex3fv( tess.xyz[ i2 ] );
+				qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] );
+				qglEnd();
+				c_edges++;
+			} else {
+				c_rejected++;
+			}
+		}
+	}
+#endif
+}
+
+/*
+=================
+RB_ShadowTessEnd
+
+triangleFromEdge[ v1 ][ v2 ]
+
+
+  set triangle from edge( v1, v2, tri )
+  if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) {
+  }
+=================
+*/
+void RB_ShadowTessEnd( void ) {
+	int		i;
+	int		numTris;
+	vec3_t	lightDir;
+	GLboolean rgba[4];
+
+	// we can only do this if we have enough space in the vertex buffers
+	if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) {
+		return;
+	}
+
+	if ( glConfig.stencilBits < 4 ) {
+		return;
+	}
+
+	VectorCopy( backEnd.currentEntity->lightDir, lightDir );
+
+	// project vertexes away from light direction
+	for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+		VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] );
+	}
+
+	// decide which triangles face the light
+	Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes );
+
+	numTris = tess.numIndexes / 3;
+	for ( i = 0 ; i < numTris ; i++ ) {
+		int		i1, i2, i3;
+		vec3_t	d1, d2, normal;
+		float	*v1, *v2, *v3;
+		float	d;
+
+		i1 = tess.indexes[ i*3 + 0 ];
+		i2 = tess.indexes[ i*3 + 1 ];
+		i3 = tess.indexes[ i*3 + 2 ];
+
+		v1 = tess.xyz[ i1 ];
+		v2 = tess.xyz[ i2 ];
+		v3 = tess.xyz[ i3 ];
+
+		VectorSubtract( v2, v1, d1 );
+		VectorSubtract( v3, v1, d2 );
+		CrossProduct( d1, d2, normal );
+
+		d = DotProduct( normal, lightDir );
+		if ( d > 0 ) {
+			facing[ i ] = 1;
+		} else {
+			facing[ i ] = 0;
+		}
+
+		// create the edges
+		R_AddEdgeDef( i1, i2, facing[ i ] );
+		R_AddEdgeDef( i2, i3, facing[ i ] );
+		R_AddEdgeDef( i3, i1, facing[ i ] );
+	}
+
+	// draw the silhouette edges
+
+	GL_Bind( tr.whiteImage );
+	qglEnable( GL_CULL_FACE );
+	GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
+	qglColor3f( 0.2f, 0.2f, 0.2f );
+
+	// don't write to the color buffer
+	qglGetBooleanv(GL_COLOR_WRITEMASK, rgba);
+	qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
+
+	qglEnable( GL_STENCIL_TEST );
+	qglStencilFunc( GL_ALWAYS, 1, 255 );
+
+	// mirrors have the culling order reversed
+	if ( backEnd.viewParms.isMirror ) {
+		qglCullFace( GL_FRONT );
+		qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
+
+		R_RenderShadowEdges();
+
+		qglCullFace( GL_BACK );
+		qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
+
+		R_RenderShadowEdges();
+	} else {
+		qglCullFace( GL_BACK );
+		qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
+
+		R_RenderShadowEdges();
+
+		qglCullFace( GL_FRONT );
+		qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
+
+		R_RenderShadowEdges();
+	}
+
+
+	// reenable writing to the color buffer
+	qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]);
+}
+
+
+/*
+=================
+RB_ShadowFinish
+
+Darken everything that is is a shadow volume.
+We have to delay this until everything has been shadowed,
+because otherwise shadows from different body parts would
+overlap and double darken.
+=================
+*/
+void RB_ShadowFinish( void ) {
+	if ( r_shadows->integer != 2 ) {
+		return;
+	}
+	if ( glConfig.stencilBits < 4 ) {
+		return;
+	}
+	qglEnable( GL_STENCIL_TEST );
+	qglStencilFunc( GL_NOTEQUAL, 0, 255 );
+
+	qglDisable (GL_CLIP_PLANE0);
+	qglDisable (GL_CULL_FACE);
+
+	GL_Bind( tr.whiteImage );
+
+    qglLoadIdentity ();
+
+	qglColor3f( 0.6f, 0.6f, 0.6f );
+	GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO );
+
+//	qglColor3f( 1, 0, 0 );
+//	GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
+
+	qglBegin( GL_QUADS );
+	qglVertex3f( -100, 100, -10 );
+	qglVertex3f( 100, 100, -10 );
+	qglVertex3f( 100, -100, -10 );
+	qglVertex3f( -100, -100, -10 );
+	qglEnd ();
+
+	qglColor4f(1,1,1,1);
+	qglDisable( GL_STENCIL_TEST );
+}
+
+
+/*
+=================
+RB_ProjectionShadowDeform
+
+=================
+*/
+void RB_ProjectionShadowDeform( void ) {
+	float	*xyz;
+	int		i;
+	float	h;
+	vec3_t	ground;
+	vec3_t	light;
+	float	groundDist;
+	float	d;
+	vec3_t	lightDir;
+
+	xyz = ( float * ) tess.xyz;
+
+	ground[0] = backEnd.or.axis[0][2];
+	ground[1] = backEnd.or.axis[1][2];
+	ground[2] = backEnd.or.axis[2][2];
+
+	groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane;
+
+	VectorCopy( backEnd.currentEntity->lightDir, lightDir );
+	d = DotProduct( lightDir, ground );
+	// don't let the shadows get too long or go negative
+	if ( d < 0.5 ) {
+		VectorMA( lightDir, (0.5 - d), ground, lightDir );
+		d = DotProduct( lightDir, ground );
+	}
+	d = 1.0 / d;
+
+	light[0] = lightDir[0] * d;
+	light[1] = lightDir[1] * d;
+	light[2] = lightDir[2] * d;
+
+	for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) {
+		h = DotProduct( xyz, ground ) + groundDist;
+
+		xyz[0] -= light[0] * h;
+		xyz[1] -= light[1] * h;
+		xyz[2] -= light[2] * h;
+	}
+}

Added: trunk/code/rend2/tr_sky.c
===================================================================
--- trunk/code/rend2/tr_sky.c	                        (rev 0)
+++ trunk/code/rend2/tr_sky.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,955 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_sky.c
+#include "tr_local.h"
+
+#define SKY_SUBDIVISIONS		8
+#define HALF_SKY_SUBDIVISIONS	(SKY_SUBDIVISIONS/2)
+
+static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2];
+static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1];
+
+/*
+===================================================================================
+
+POLYGON TO BOX SIDE PROJECTION
+
+===================================================================================
+*/
+
+static vec3_t sky_clip[6] = 
+{
+	{1,1,0},
+	{1,-1,0},
+	{0,-1,1},
+	{0,1,1},
+	{1,0,1},
+	{-1,0,1} 
+};
+
+static float	sky_mins[2][6], sky_maxs[2][6];
+static float	sky_min, sky_max;
+
+/*
+================
+AddSkyPolygon
+================
+*/
+static void AddSkyPolygon (int nump, vec3_t vecs) 
+{
+	int		i,j;
+	vec3_t	v, av;
+	float	s, t, dv;
+	int		axis;
+	float	*vp;
+	// s = [0]/[2], t = [1]/[2]
+	static int	vec_to_st[6][3] =
+	{
+		{-2,3,1},
+		{2,3,-1},
+
+		{1,3,2},
+		{-1,3,-2},
+
+		{-2,-1,3},
+		{-2,1,-3}
+
+	//	{-1,2,3},
+	//	{1,2,-3}
+	};
+
+	// decide which face it maps to
+	VectorCopy (vec3_origin, v);
+	for (i=0, vp=vecs ; i<nump ; i++, vp+=3)
+	{
+		VectorAdd (vp, v, v);
+	}
+	av[0] = fabs(v[0]);
+	av[1] = fabs(v[1]);
+	av[2] = fabs(v[2]);
+	if (av[0] > av[1] && av[0] > av[2])
+	{
+		if (v[0] < 0)
+			axis = 1;
+		else
+			axis = 0;
+	}
+	else if (av[1] > av[2] && av[1] > av[0])
+	{
+		if (v[1] < 0)
+			axis = 3;
+		else
+			axis = 2;
+	}
+	else
+	{
+		if (v[2] < 0)
+			axis = 5;
+		else
+			axis = 4;
+	}
+
+	// project new texture coords
+	for (i=0 ; i<nump ; i++, vecs+=3)
+	{
+		j = vec_to_st[axis][2];
+		if (j > 0)
+			dv = vecs[j - 1];
+		else
+			dv = -vecs[-j - 1];
+		if (dv < 0.001)
+			continue;	// don't divide by zero
+		j = vec_to_st[axis][0];
+		if (j < 0)
+			s = -vecs[-j -1] / dv;
+		else
+			s = vecs[j-1] / dv;
+		j = vec_to_st[axis][1];
+		if (j < 0)
+			t = -vecs[-j -1] / dv;
+		else
+			t = vecs[j-1] / dv;
+
+		if (s < sky_mins[0][axis])
+			sky_mins[0][axis] = s;
+		if (t < sky_mins[1][axis])
+			sky_mins[1][axis] = t;
+		if (s > sky_maxs[0][axis])
+			sky_maxs[0][axis] = s;
+		if (t > sky_maxs[1][axis])
+			sky_maxs[1][axis] = t;
+	}
+}
+
+#define	ON_EPSILON		0.1f			// point on plane side epsilon
+#define	MAX_CLIP_VERTS	64
+/*
+================
+ClipSkyPolygon
+================
+*/
+static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) 
+{
+	float	*norm;
+	float	*v;
+	qboolean	front, back;
+	float	d, e;
+	float	dists[MAX_CLIP_VERTS];
+	int		sides[MAX_CLIP_VERTS];
+	vec3_t	newv[2][MAX_CLIP_VERTS];
+	int		newc[2];
+	int		i, j;
+
+	if (nump > MAX_CLIP_VERTS-2)
+		ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS");
+	if (stage == 6)
+	{	// fully clipped, so draw it
+		AddSkyPolygon (nump, vecs);
+		return;
+	}
+
+	front = back = qfalse;
+	norm = sky_clip[stage];
+	for (i=0, v = vecs ; i<nump ; i++, v+=3)
+	{
+		d = DotProduct (v, norm);
+		if (d > ON_EPSILON)
+		{
+			front = qtrue;
+			sides[i] = SIDE_FRONT;
+		}
+		else if (d < -ON_EPSILON)
+		{
+			back = qtrue;
+			sides[i] = SIDE_BACK;
+		}
+		else
+			sides[i] = SIDE_ON;
+		dists[i] = d;
+	}
+
+	if (!front || !back)
+	{	// not clipped
+		ClipSkyPolygon (nump, vecs, stage+1);
+		return;
+	}
+
+	// clip it
+	sides[i] = sides[0];
+	dists[i] = dists[0];
+	VectorCopy (vecs, (vecs+(i*3)) );
+	newc[0] = newc[1] = 0;
+
+	for (i=0, v = vecs ; i<nump ; i++, v+=3)
+	{
+		switch (sides[i])
+		{
+		case SIDE_FRONT:
+			VectorCopy (v, newv[0][newc[0]]);
+			newc[0]++;
+			break;
+		case SIDE_BACK:
+			VectorCopy (v, newv[1][newc[1]]);
+			newc[1]++;
+			break;
+		case SIDE_ON:
+			VectorCopy (v, newv[0][newc[0]]);
+			newc[0]++;
+			VectorCopy (v, newv[1][newc[1]]);
+			newc[1]++;
+			break;
+		}
+
+		if (sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+			continue;
+
+		d = dists[i] / (dists[i] - dists[i+1]);
+		for (j=0 ; j<3 ; j++)
+		{
+			e = v[j] + d*(v[j+3] - v[j]);
+			newv[0][newc[0]][j] = e;
+			newv[1][newc[1]][j] = e;
+		}
+		newc[0]++;
+		newc[1]++;
+	}
+
+	// continue
+	ClipSkyPolygon (newc[0], newv[0][0], stage+1);
+	ClipSkyPolygon (newc[1], newv[1][0], stage+1);
+}
+
+/*
+==============
+ClearSkyBox
+==============
+*/
+static void ClearSkyBox (void) {
+	int		i;
+
+	for (i=0 ; i<6 ; i++) {
+		sky_mins[0][i] = sky_mins[1][i] = 9999;
+		sky_maxs[0][i] = sky_maxs[1][i] = -9999;
+	}
+}
+
+/*
+================
+RB_ClipSkyPolygons
+================
+*/
+void RB_ClipSkyPolygons( shaderCommands_t *input )
+{
+	vec3_t		p[5];	// need one extra point for clipping
+	int			i, j;
+
+	ClearSkyBox();
+
+	for ( i = 0; i < input->numIndexes; i += 3 )
+	{
+		for (j = 0 ; j < 3 ; j++) 
+		{
+			VectorSubtract( input->xyz[input->indexes[i+j]],
+							backEnd.viewParms.or.origin, 
+							p[j] );
+		}
+		ClipSkyPolygon( 3, p[0], 0 );
+	}
+}
+
+/*
+===================================================================================
+
+CLOUD VERTEX GENERATION
+
+===================================================================================
+*/
+
+/*
+** MakeSkyVec
+**
+** Parms: s, t range from -1 to 1
+*/
+static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ )
+{
+	// 1 = s, 2 = t, 3 = 2048
+	static int	st_to_vec[6][3] =
+	{
+		{3,-1,2},
+		{-3,1,2},
+
+		{1,3,2},
+		{-1,-3,2},
+
+		{-2,-1,3},		// 0 degrees yaw, look straight up
+		{2,-1,-3}		// look straight down
+	};
+
+	vec3_t		b;
+	int			j, k;
+	float	boxSize;
+
+	boxSize = backEnd.viewParms.zFar / 1.75;		// div sqrt(3)
+	b[0] = s*boxSize;
+	b[1] = t*boxSize;
+	b[2] = boxSize;
+
+	for (j=0 ; j<3 ; j++)
+	{
+		k = st_to_vec[axis][j];
+		if (k < 0)
+		{
+			outXYZ[j] = -b[-k - 1];
+		}
+		else
+		{
+			outXYZ[j] = b[k - 1];
+		}
+	}
+
+	// avoid bilerp seam
+	s = (s+1)*0.5;
+	t = (t+1)*0.5;
+	if (s < sky_min)
+	{
+		s = sky_min;
+	}
+	else if (s > sky_max)
+	{
+		s = sky_max;
+	}
+
+	if (t < sky_min)
+	{
+		t = sky_min;
+	}
+	else if (t > sky_max)
+	{
+		t = sky_max;
+	}
+
+	t = 1.0 - t;
+
+
+	if ( outSt )
+	{
+		outSt[0] = s;
+		outSt[1] = t;
+	}
+}
+
+static int	sky_texorder[6] = {0,2,1,3,4,5};
+static vec3_t	s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1];
+static float	s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2];
+
+static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] )
+{
+	int s, t;
+	int firstVertex = tess.numVertexes;
+	//int firstIndex = tess.numIndexes;
+	vec4_t color;
+
+	//tess.numVertexes = 0;
+	//tess.numIndexes = 0;
+	tess.firstIndex = tess.numIndexes;
+	
+	GL_Bind( image );
+	GL_Cull( CT_TWO_SIDED );
+
+	for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ )
+	{
+		for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ )
+		{
+			tess.xyz[tess.numVertexes][0] = s_skyPoints[t][s][0];
+			tess.xyz[tess.numVertexes][1] = s_skyPoints[t][s][1];
+			tess.xyz[tess.numVertexes][2] = s_skyPoints[t][s][2];
+			tess.xyz[tess.numVertexes][3] = 1.0;
+
+			tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0];
+			tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1];
+
+			tess.numVertexes++;
+
+			if(tess.numVertexes >= SHADER_MAX_VERTEXES)
+			{
+				ri.Error(ERR_DROP, "SHADER_MAX_VERTEXES hit in DrawSkySideVBO()\n");
+			}
+		}
+	}
+
+	for ( t = 0; t < maxs[1] - mins[1]; t++ )
+	{
+		for ( s = 0; s < maxs[0] - mins[0]; s++ )
+		{
+			if (tess.numIndexes + 6 >= SHADER_MAX_INDEXES)
+			{
+				ri.Error(ERR_DROP, "SHADER_MAX_INDEXES hit in DrawSkySideVBO()\n");
+			}
+
+			tess.indexes[tess.numIndexes++] =  s +       t      * (maxs[0] - mins[0] + 1) + firstVertex;
+			tess.indexes[tess.numIndexes++] =  s +      (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex;
+			tess.indexes[tess.numIndexes++] = (s + 1) +  t      * (maxs[0] - mins[0] + 1) + firstVertex;
+
+			tess.indexes[tess.numIndexes++] = (s + 1) +  t      * (maxs[0] - mins[0] + 1) + firstVertex;
+			tess.indexes[tess.numIndexes++] =  s +      (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex;
+			tess.indexes[tess.numIndexes++] = (s + 1) + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex;
+		}
+	}
+
+	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
+	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+/*
+	{
+		shaderProgram_t *sp = &tr.textureColorShader;
+
+		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
+		GLSL_BindProgram(sp);
+		
+		GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		
+		color[0] = 
+		color[1] = 
+		color[2] = tr.identityLight;
+		color[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color);
+	}
+*/
+	{
+		shaderProgram_t *sp = &tr.lightallShader[0];
+		matrix_t matrix;
+
+		GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
+		GLSL_BindProgram(sp);
+		
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+		
+		color[0] = 
+		color[1] = 
+		color[2] = tr.identityLight;
+		color[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, color);
+
+		color[0] = 
+		color[1] = 
+		color[2] = 
+		color[3] = 0.0f;
+		GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, color);
+
+		Matrix16Identity(matrix);
+		GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix);
+	}
+
+	R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex);
+
+	//qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(GL_INDEX_TYPE)));
+	
+	//R_BindNullVBO();
+	//R_BindNullIBO();
+
+	tess.numIndexes = tess.firstIndex;
+	tess.numVertexes = firstVertex;
+	tess.firstIndex = 0;
+}
+
+static void DrawSkyBox( shader_t *shader )
+{
+	int		i;
+
+	sky_min = 0;
+	sky_max = 1;
+
+	Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) );
+
+	for (i=0 ; i<6 ; i++)
+	{
+		int sky_mins_subd[2], sky_maxs_subd[2];
+		int s, t;
+
+		sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+
+		if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) ||
+			 ( sky_mins[1][i] >= sky_maxs[1][i] ) )
+		{
+			continue;
+		}
+
+		sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS;
+		sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS;
+		sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS;
+		sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS;
+
+		if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
+		if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS )
+			sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+		if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
+		if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+		//
+		// iterate through the subdivisions
+		//
+		for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ )
+		{
+			for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ )
+			{
+				MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							i, 
+							s_skyTexCoords[t][s], 
+							s_skyPoints[t][s] );
+			}
+		}
+
+		DrawSkySide( shader->sky.outerbox[sky_texorder[i]],
+			         sky_mins_subd,
+					 sky_maxs_subd );
+	}
+
+}
+
+static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes )
+{
+	int s, t;
+	int vertexStart = tess.numVertexes;
+	int tHeight, sWidth;
+
+	tHeight = maxs[1] - mins[1] + 1;
+	sWidth = maxs[0] - mins[0] + 1;
+
+	for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ )
+	{
+		for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ )
+		{
+			VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[tess.numVertexes] );
+			tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0];
+			tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1];
+
+			tess.numVertexes++;
+
+			if ( tess.numVertexes >= SHADER_MAX_VERTEXES )
+			{
+				ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()" );
+			}
+		}
+	}
+
+	// only add indexes for one pass, otherwise it would draw multiple times for each pass
+	if ( addIndexes ) {
+		for ( t = 0; t < tHeight-1; t++ )
+		{	
+			for ( s = 0; s < sWidth-1; s++ )
+			{
+				tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth );
+				tess.numIndexes++;
+				tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth );
+				tess.numIndexes++;
+				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth );
+				tess.numIndexes++;
+
+				tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth );
+				tess.numIndexes++;
+				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth );
+				tess.numIndexes++;
+				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth );
+				tess.numIndexes++;
+			}
+		}
+	}
+}
+
+static void FillCloudBox( const shader_t *shader, int stage )
+{
+	int i;
+
+	for ( i =0; i < 6; i++ )
+	{
+		int sky_mins_subd[2], sky_maxs_subd[2];
+		int s, t;
+		float MIN_T;
+
+		if ( 1 ) // FIXME? shader->sky.fullClouds )
+		{
+			MIN_T = -HALF_SKY_SUBDIVISIONS;
+
+			// still don't want to draw the bottom, even if fullClouds
+			if ( i == 5 )
+				continue;
+		}
+		else
+		{
+			switch( i )
+			{
+			case 0:
+			case 1:
+			case 2:
+			case 3:
+				MIN_T = -1;
+				break;
+			case 5:
+				// don't draw clouds beneath you
+				continue;
+			case 4:		// top
+			default:
+				MIN_T = -HALF_SKY_SUBDIVISIONS;
+				break;
+			}
+		}
+
+		sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+		sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+
+		if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) ||
+			 ( sky_mins[1][i] >= sky_maxs[1][i] ) )
+		{
+			continue;
+		}
+
+		sky_mins_subd[0] = ri.ftol(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS);
+		sky_mins_subd[1] = ri.ftol(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS);
+		sky_maxs_subd[0] = ri.ftol(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS);
+		sky_maxs_subd[1] = ri.ftol(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS);
+
+		if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
+		if ( sky_mins_subd[1] < MIN_T )
+			sky_mins_subd[1] = MIN_T;
+		else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) 
+			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+		if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
+		else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
+		if ( sky_maxs_subd[1] < MIN_T )
+			sky_maxs_subd[1] = MIN_T;
+		else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) 
+			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+		//
+		// iterate through the subdivisions
+		//
+		for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ )
+		{
+			for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ )
+			{
+				MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							i, 
+							NULL,
+							s_skyPoints[t][s] );
+
+				s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0];
+				s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1];
+			}
+		}
+
+		// only add indexes for first stage
+		FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) );
+	}
+}
+
+/*
+** R_BuildCloudData
+*/
+void R_BuildCloudData( shaderCommands_t *input )
+{
+	int			i;
+	shader_t	*shader;
+
+	shader = input->shader;
+
+	assert( shader->isSky );
+
+	sky_min = 1.0 / 256.0f;		// FIXME: not correct?
+	sky_max = 255.0 / 256.0f;
+
+	// set up for drawing
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+
+	if ( shader->sky.cloudHeight )
+	{
+		for ( i = 0; i < MAX_SHADER_STAGES; i++ )
+		{
+			if ( !tess.xstages[i] ) {
+				break;
+			}
+			FillCloudBox( shader, i );
+		}
+	}
+}
+
+/*
+** R_InitSkyTexCoords
+** Called when a sky shader is parsed
+*/
+#define SQR( a ) ((a)*(a))
+void R_InitSkyTexCoords( float heightCloud )
+{
+	int i, s, t;
+	float radiusWorld = 4096;
+	float p;
+	float sRad, tRad;
+	vec3_t skyVec;
+	vec3_t v;
+
+	// init zfar so MakeSkyVec works even though
+	// a world hasn't been bounded
+	backEnd.viewParms.zFar = 1024;
+
+	for ( i = 0; i < 6; i++ )
+	{
+		for ( t = 0; t <= SKY_SUBDIVISIONS; t++ )
+		{
+			for ( s = 0; s <= SKY_SUBDIVISIONS; s++ )
+			{
+				// compute vector from view origin to sky side integral point
+				MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, 
+							i, 
+							NULL,
+							skyVec );
+
+				// compute parametric value 'p' that intersects with cloud layer
+				p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) *
+					( -2 * skyVec[2] * radiusWorld + 
+					   2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + 
+					             2 * SQR( skyVec[0] ) * radiusWorld * heightCloud +
+								 SQR( skyVec[0] ) * SQR( heightCloud ) + 
+								 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud +
+								 SQR( skyVec[1] ) * SQR( heightCloud ) + 
+								 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud +
+								 SQR( skyVec[2] ) * SQR( heightCloud ) ) );
+
+				s_cloudTexP[i][t][s] = p;
+
+				// compute intersection point based on p
+				VectorScale( skyVec, p, v );
+				v[2] += radiusWorld;
+
+				// compute vector from world origin to intersection point 'v'
+				VectorNormalize( v );
+
+				sRad = Q_acos( v[0] );
+				tRad = Q_acos( v[1] );
+
+				s_cloudTexCoords[i][t][s][0] = sRad;
+				s_cloudTexCoords[i][t][s][1] = tRad;
+			}
+		}
+	}
+}
+
+//======================================================================================
+
+/*
+** RB_DrawSun
+*/
+void RB_DrawSun( void ) {
+	float		size;
+	float		dist;
+	vec3_t		origin, vec1, vec2;
+	vec3_t		temp;
+
+	if ( !backEnd.skyRenderedThisView ) {
+		return;
+	}
+	if ( !r_drawSun->integer ) {
+		return;
+	}
+
+	//qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
+	//qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]);
+	{
+		// FIXME: this could be a lot cleaner
+		matrix_t trans, product;
+
+		Matrix16Translation( backEnd.viewParms.or.origin, trans );
+		Matrix16Multiply( backEnd.viewParms.world.modelMatrix, trans, product );
+		GL_SetModelviewMatrix( product );
+	}
+
+	dist = 	backEnd.viewParms.zFar / 1.75;		// div sqrt(3)
+	size = dist * 0.4;
+
+	VectorScale( tr.sunDirection, dist, origin );
+	PerpendicularVector( vec1, tr.sunDirection );
+	CrossProduct( tr.sunDirection, vec1, vec2 );
+
+	VectorScale( vec1, size, vec1 );
+	VectorScale( vec2, size, vec2 );
+
+	// farthest depth range
+	qglDepthRange( 1.0, 1.0 );
+
+	// FIXME: use quad stamp
+	RB_BeginSurface( tr.sunShader, tess.fogNum );
+		VectorCopy( origin, temp );
+		VectorSubtract( temp, vec1, temp );
+		VectorSubtract( temp, vec2, temp );
+		VectorCopy( temp, tess.xyz[tess.numVertexes] );
+		tess.texCoords[tess.numVertexes][0][0] = 0;
+		tess.texCoords[tess.numVertexes][0][1] = 0;
+		tess.vertexColors[tess.numVertexes][0] = 1.0f;
+		tess.vertexColors[tess.numVertexes][1] = 1.0f;
+		tess.vertexColors[tess.numVertexes][2] = 1.0f;
+		tess.numVertexes++;
+
+		VectorCopy( origin, temp );
+		VectorAdd( temp, vec1, temp );
+		VectorSubtract( temp, vec2, temp );
+		VectorCopy( temp, tess.xyz[tess.numVertexes] );
+		tess.texCoords[tess.numVertexes][0][0] = 0;
+		tess.texCoords[tess.numVertexes][0][1] = 1;
+		tess.vertexColors[tess.numVertexes][0] = 1.0f;
+		tess.vertexColors[tess.numVertexes][1] = 1.0f;
+		tess.vertexColors[tess.numVertexes][2] = 1.0f;
+		tess.numVertexes++;
+
+		VectorCopy( origin, temp );
+		VectorAdd( temp, vec1, temp );
+		VectorAdd( temp, vec2, temp );
+		VectorCopy( temp, tess.xyz[tess.numVertexes] );
+		tess.texCoords[tess.numVertexes][0][0] = 1;
+		tess.texCoords[tess.numVertexes][0][1] = 1;
+		tess.vertexColors[tess.numVertexes][0] = 1.0f;
+		tess.vertexColors[tess.numVertexes][1] = 1.0f;
+		tess.vertexColors[tess.numVertexes][2] = 1.0f;
+		tess.numVertexes++;
+
+		VectorCopy( origin, temp );
+		VectorSubtract( temp, vec1, temp );
+		VectorAdd( temp, vec2, temp );
+		VectorCopy( temp, tess.xyz[tess.numVertexes] );
+		tess.texCoords[tess.numVertexes][0][0] = 1;
+		tess.texCoords[tess.numVertexes][0][1] = 0;
+		tess.vertexColors[tess.numVertexes][0] = 1.0f;
+		tess.vertexColors[tess.numVertexes][1] = 1.0f;
+		tess.vertexColors[tess.numVertexes][2] = 1.0f;
+		tess.numVertexes++;
+
+		tess.indexes[tess.numIndexes++] = 0;
+		tess.indexes[tess.numIndexes++] = 1;
+		tess.indexes[tess.numIndexes++] = 2;
+		tess.indexes[tess.numIndexes++] = 0;
+		tess.indexes[tess.numIndexes++] = 2;
+		tess.indexes[tess.numIndexes++] = 3;
+
+	RB_EndSurface();
+
+	// back to normal depth range
+	qglDepthRange( 0.0, 1.0 );
+}
+
+
+
+
+/*
+================
+RB_StageIteratorSky
+
+All of the visible sky triangles are in tess
+
+Other things could be stuck in here, like birds in the sky, etc
+================
+*/
+void RB_StageIteratorSky( void ) {
+	if ( r_fastsky->integer ) {
+		return;
+	}
+
+	// go through all the polygons and project them onto
+	// the sky box to see which blocks on each side need
+	// to be drawn
+	RB_ClipSkyPolygons( &tess );
+
+	// r_showsky will let all the sky blocks be drawn in
+	// front of everything to allow developers to see how
+	// much sky is getting sucked in
+	if ( r_showsky->integer ) {
+		qglDepthRange( 0.0, 0.0 );
+	} else {
+		qglDepthRange( 1.0, 1.0 );
+	}
+
+	// draw the outer skybox
+	if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) {
+		matrix_t oldmodelview;
+		
+		GL_State( 0 );
+		//qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]);
+
+		{
+			// FIXME: this could be a lot cleaner
+			matrix_t trans, product;
+
+			Matrix16Copy( glState.modelview, oldmodelview );
+			Matrix16Translation( backEnd.viewParms.or.origin, trans );
+			Matrix16Multiply( glState.modelview, trans, product );
+			GL_SetModelviewMatrix( product );
+
+		}
+
+		DrawSkyBox( tess.shader );
+
+		GL_SetModelviewMatrix( oldmodelview );
+	}
+
+	// generate the vertexes for all the clouds, which will be drawn
+	// by the generic shader routine
+	R_BuildCloudData( &tess );
+
+	RB_StageIteratorGeneric();
+
+	// draw the inner skybox
+
+
+	// back to normal depth range
+	qglDepthRange( 0.0, 1.0 );
+
+	// note that sky was drawn so we will draw a sun later
+	backEnd.skyRenderedThisView = qtrue;
+}
+
+
+
+
+

Added: trunk/code/rend2/tr_subs.c
===================================================================
--- trunk/code/rend2/tr_subs.c	                        (rev 0)
+++ trunk/code/rend2/tr_subs.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,48 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01 at gmail.com)
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_subs.c - common function replacements for modular renderer
+
+#include "tr_local.h"
+
+void QDECL Com_Printf( const char *msg, ... )
+{
+	va_list         argptr;
+	char            text[1024];
+
+	va_start(argptr, msg);
+	Q_vsnprintf(text, sizeof(text), msg, argptr);
+	va_end(argptr);
+
+	ri.Printf(PRINT_ALL, "%s", text);
+}
+
+void QDECL Com_Error( int level, const char *error, ... )
+{
+	va_list         argptr;
+	char            text[1024];
+
+	va_start(argptr, error);
+	Q_vsnprintf(text, sizeof(text), error, argptr);
+	va_end(argptr);
+
+	ri.Error(level, "%s", text);
+}

Added: trunk/code/rend2/tr_surface.c
===================================================================
--- trunk/code/rend2/tr_surface.c	                        (rev 0)
+++ trunk/code/rend2/tr_surface.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,1691 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_surf.c
+#include "tr_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+/*
+
+  THIS ENTIRE FILE IS BACK END
+
+backEnd.currentEntity will be valid.
+
+Tess_Begin has already been called for the surface's shader.
+
+The modelview matrix will be set.
+
+It is safe to actually issue drawing commands here if you don't want to
+use the shader system.
+*/
+
+
+//============================================================================
+
+
+/*
+==============
+RB_CheckOverflow
+==============
+*/
+void RB_CheckOverflow( int verts, int indexes ) {
+	if (tess.numVertexes + verts < SHADER_MAX_VERTEXES
+		&& tess.numIndexes + indexes < SHADER_MAX_INDEXES) {
+		return;
+	}
+
+	RB_EndSurface();
+
+	if ( verts >= SHADER_MAX_VERTEXES ) {
+		ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES );
+	}
+	if ( indexes >= SHADER_MAX_INDEXES ) {
+		ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES );
+	}
+
+	RB_BeginSurface(tess.shader, tess.fogNum );
+}
+
+void RB_CheckVBOandIBO(VBO_t *vbo, IBO_t *ibo)
+{
+	if (!(vbo == glState.currentVBO && ibo == glState.currentIBO) || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES)
+	{
+		RB_EndSurface();
+		RB_BeginSurface(tess.shader, tess.fogNum);
+
+		R_BindVBO(vbo);
+		R_BindIBO(ibo);
+	}
+
+	if (vbo != tess.vbo && ibo != tess.ibo)
+		tess.useInternalVBO = qfalse;
+}
+
+
+/*
+==============
+RB_AddQuadStampExt
+==============
+*/
+void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], float s1, float t1, float s2, float t2 ) {
+	vec3_t		normal;
+	int			ndx;
+
+	RB_CHECKOVERFLOW( 4, 6 );
+
+	ndx = tess.numVertexes;
+
+	// triangle indexes for a simple quad
+	tess.indexes[ tess.numIndexes ] = ndx;
+	tess.indexes[ tess.numIndexes + 1 ] = ndx + 1;
+	tess.indexes[ tess.numIndexes + 2 ] = ndx + 3;
+
+	tess.indexes[ tess.numIndexes + 3 ] = ndx + 3;
+	tess.indexes[ tess.numIndexes + 4 ] = ndx + 1;
+	tess.indexes[ tess.numIndexes + 5 ] = ndx + 2;
+
+	tess.xyz[ndx][0] = origin[0] + left[0] + up[0];
+	tess.xyz[ndx][1] = origin[1] + left[1] + up[1];
+	tess.xyz[ndx][2] = origin[2] + left[2] + up[2];
+
+	tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0];
+	tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1];
+	tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2];
+
+	tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0];
+	tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1];
+	tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2];
+
+	tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0];
+	tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1];
+	tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2];
+
+
+	// constant normal all the way around
+	VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal );
+
+	tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0];
+	tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1];
+	tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2];
+	
+	// standard square texture coordinates
+	tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1;
+	tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1;
+
+	tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2;
+	tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1;
+
+	tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2;
+	tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2;
+
+	tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1;
+	tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2;
+
+	// constant color all the way around
+	// should this be identity and let the shader specify from entity?
+	tess.vertexColors[ndx][0] = color[0];
+	tess.vertexColors[ndx][1] = color[1];
+	tess.vertexColors[ndx][2] = color[2];
+	tess.vertexColors[ndx][3] = color[3];
+
+	tess.vertexColors[ndx+1][0] = color[0];
+	tess.vertexColors[ndx+1][1] = color[1];
+	tess.vertexColors[ndx+1][2] = color[2];
+	tess.vertexColors[ndx+1][3] = color[3];
+
+	tess.vertexColors[ndx+2][0] = color[0];
+	tess.vertexColors[ndx+2][1] = color[1];
+	tess.vertexColors[ndx+2][2] = color[2];
+	tess.vertexColors[ndx+2][3] = color[3];
+
+	tess.vertexColors[ndx+3][0] = color[0];
+	tess.vertexColors[ndx+3][1] = color[1];
+	tess.vertexColors[ndx+3][2] = color[2];
+	tess.vertexColors[ndx+3][3] = color[3];
+
+	tess.numVertexes += 4;
+	tess.numIndexes += 6;
+}
+
+/*
+==============
+RB_AddQuadStamp
+==============
+*/
+void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] ) {
+	RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 );
+}
+
+
+/*
+==============
+RB_InstantQuad
+
+based on Tess_InstantQuad from xreal
+==============
+*/
+void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4])
+{
+	GLimp_LogComment("--- RB_InstantQuad2 ---\n");
+
+	tess.numVertexes = 0;
+	tess.numIndexes = 0;
+	tess.firstIndex = 0;
+
+	VectorCopy4(quadVerts[0], tess.xyz[tess.numVertexes]);
+	VectorCopy2(texCoords[0], tess.texCoords[tess.numVertexes][0]);
+	tess.numVertexes++;
+
+	VectorCopy4(quadVerts[1], tess.xyz[tess.numVertexes]);
+	VectorCopy2(texCoords[1], tess.texCoords[tess.numVertexes][0]);
+	tess.numVertexes++;
+
+	VectorCopy4(quadVerts[2], tess.xyz[tess.numVertexes]);
+	VectorCopy2(texCoords[2], tess.texCoords[tess.numVertexes][0]);
+	tess.numVertexes++;
+
+	VectorCopy4(quadVerts[3], tess.xyz[tess.numVertexes]);
+	VectorCopy2(texCoords[3], tess.texCoords[tess.numVertexes][0]);
+	tess.numVertexes++;
+
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 1;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 0;
+	tess.indexes[tess.numIndexes++] = 2;
+	tess.indexes[tess.numIndexes++] = 3;
+
+	RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
+
+	GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
+
+	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex);
+
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+}
+
+
+void RB_InstantQuad(vec4_t quadVerts[4])
+{
+	vec4_t color;
+	vec2_t texCoords[4];
+	vec2_t invTexRes;
+
+	VectorSet4(color, 1, 1, 1, 1);
+
+	texCoords[0][0] = 0;
+	texCoords[0][1] = 0;
+
+	texCoords[1][0] = 1;
+	texCoords[1][1] = 0;
+
+	texCoords[2][0] = 1;
+	texCoords[2][1] = 1;
+
+	texCoords[3][0] = 0;
+	texCoords[3][1] = 1;
+
+	invTexRes[0] = 1.0f / 256.0f;
+	invTexRes[1] = 1.0f / 256.0f;
+
+	GLSL_BindProgram(&tr.textureColorShader);
+	
+	GLSL_SetUniformMatrix16(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+	GLSL_SetUniformVec4(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR, color);
+	GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes);
+	GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax);
+	GLSL_SetUniformVec3(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear);
+
+	RB_InstantQuad2(quadVerts, texCoords); //, color, &tr.textureColorShader, invTexRes);
+}
+
+
+/*
+==============
+RB_SurfaceSprite
+==============
+*/
+static void RB_SurfaceSprite( void ) {
+	vec3_t		left, up;
+	float		radius;
+	float			colors[4];
+	trRefEntity_t	*ent = backEnd.currentEntity;
+
+	// calculate the xyz locations for the four corners
+	radius = ent->e.radius;
+	if ( ent->e.rotation == 0 ) {
+		VectorScale( backEnd.viewParms.or.axis[1], radius, left );
+		VectorScale( backEnd.viewParms.or.axis[2], radius, up );
+	} else {
+		float	s, c;
+		float	ang;
+		
+		ang = M_PI * ent->e.rotation / 180;
+		s = sin( ang );
+		c = cos( ang );
+
+		VectorScale( backEnd.viewParms.or.axis[1], c * radius, left );
+		VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left );
+
+		VectorScale( backEnd.viewParms.or.axis[2], c * radius, up );
+		VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up );
+	}
+	if ( backEnd.viewParms.isMirror ) {
+		VectorSubtract( vec3_origin, left, left );
+	}
+
+#ifdef REACTION
+	if (ent->e.renderfx & RF_SUNFLARE)
+	{
+		if (backEnd.viewHasSunFlare)
+		{
+			ri.Printf(PRINT_WARNING, "Multiple sun flares not supported\n");
+			return;
+		}
+		if (R_CullPointAndRadiusEx(ent->e.origin, ent->e.radius, backEnd.viewParms.frustum, ARRAY_LEN(backEnd.viewParms.frustum)) == CULL_OUT)
+			return;
+		colors[0] = colors[1] = colors[2] = colors[3] = ent->e.shaderRGBA[glRefConfig.framebufferObject] / 255.0f;
+		if (colors[0] == 0)
+			return;
+		backEnd.viewHasSunFlare = qtrue;
+		backEnd.frameHasSunFlare = qtrue;
+	}
+	else
+#endif
+	{
+		VectorScale4(ent->e.shaderRGBA, 1.0f / 255.0f, colors);
+	}
+
+	RB_AddQuadStamp( ent->e.origin, left, up, colors );
+}
+
+
+/*
+=============
+RB_SurfacePolychain
+=============
+*/
+static void RB_SurfacePolychain( srfPoly_t *p ) {
+	int		i;
+	int		numv;
+
+	RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) );
+
+	// fan triangles into the tess array
+	numv = tess.numVertexes;
+	for ( i = 0; i < p->numVerts; i++ ) {
+		VectorCopy( p->verts[i].xyz, tess.xyz[numv] );
+		tess.texCoords[numv][0][0] = p->verts[i].st[0];
+		tess.texCoords[numv][0][1] = p->verts[i].st[1];
+		tess.vertexColors[numv][0] = p->verts[ i ].modulate[0] / 255.0f;
+		tess.vertexColors[numv][1] = p->verts[ i ].modulate[1] / 255.0f;
+		tess.vertexColors[numv][2] = p->verts[ i ].modulate[2] / 255.0f;
+		tess.vertexColors[numv][3] = p->verts[ i ].modulate[3] / 255.0f;
+
+		numv++;
+	}
+
+	// generate fan indexes into the tess array
+	for ( i = 0; i < p->numVerts-2; i++ ) {
+		tess.indexes[tess.numIndexes + 0] = tess.numVertexes;
+		tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1;
+		tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2;
+		tess.numIndexes += 3;
+	}
+
+	tess.numVertexes = numv;
+}
+
+static void RB_SurfaceHelper( int numVerts, srfVert_t *verts, int numTriangles, srfTriangle_t *triangles, int dlightBits, int pshadowBits)
+{
+	int			i;
+	srfTriangle_t  *tri;
+	srfVert_t      *dv;
+	float          *xyz, *normal, *texCoords, *lightCoords, *lightdir;
+#ifdef USE_VERT_TANGENT_SPACE
+	float          *tangent, *bitangent;
+#endif
+	glIndex_t      *index;
+	float		*color;
+
+	RB_CheckVBOandIBO(tess.vbo, tess.ibo);
+
+	RB_CHECKOVERFLOW( numVerts, numTriangles * 3 );
+
+	tri = triangles;
+	index = &tess.indexes[ tess.numIndexes ];
+	for ( i = 0 ; i < numTriangles ; i++, tri++ ) {
+		*index++ = tess.numVertexes + tri->indexes[0];
+		*index++ = tess.numVertexes + tri->indexes[1];
+		*index++ = tess.numVertexes + tri->indexes[2];
+	}
+	tess.numIndexes += numTriangles * 3;
+
+	if ( tess.shader->vertexAttribs & ATTR_POSITION )
+	{
+		dv = verts;
+		xyz = tess.xyz[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, xyz+=4 )
+			VectorCopy(dv->xyz, xyz);
+	}
+
+	if ( tess.shader->vertexAttribs & ATTR_NORMAL )
+	{
+		dv = verts;
+		normal = tess.normal[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, normal+=4 )
+			VectorCopy(dv->normal, normal);
+	}
+
+#ifdef USE_VERT_TANGENT_SPACE
+	if ( tess.shader->vertexAttribs & ATTR_TANGENT )
+	{
+		dv = verts;
+		tangent = tess.tangent[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, tangent+=4 )
+			VectorCopy(dv->tangent, tangent);
+	}
+
+	if ( tess.shader->vertexAttribs & ATTR_BITANGENT )
+	{
+		dv = verts;
+		bitangent = tess.bitangent[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, bitangent+=4 )
+			VectorCopy(dv->bitangent, bitangent);
+	}
+#endif
+
+	if ( tess.shader->vertexAttribs & ATTR_TEXCOORD )
+	{
+		dv = verts;
+		texCoords = tess.texCoords[ tess.numVertexes ][0];
+		for ( i = 0 ; i < numVerts ; i++, dv++, texCoords+=4 )
+			VectorCopy2(dv->st, texCoords);
+	}
+
+	if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD )
+	{
+		dv = verts;
+		lightCoords = tess.texCoords[ tess.numVertexes ][1];
+		for ( i = 0 ; i < numVerts ; i++, dv++, lightCoords+=4 )
+			VectorCopy2(dv->lightmap, lightCoords);
+	}
+
+	if ( tess.shader->vertexAttribs & ATTR_COLOR )
+	{
+		dv = verts;
+		color = tess.vertexColors[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, color+=4 )
+			VectorCopy4(dv->vertexColors, color);
+	}
+
+	if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
+	{
+		dv = verts;
+		lightdir = tess.lightdir[ tess.numVertexes ];
+		for ( i = 0 ; i < numVerts ; i++, dv++, lightdir+=4 )
+			VectorCopy(dv->lightdir, lightdir);
+	}
+
+#if 0  // nothing even uses vertex dlightbits
+	for ( i = 0 ; i < numVerts ; i++ ) {
+		tess.vertexDlightBits[ tess.numVertexes + i ] = dlightBits;
+	}
+#endif
+
+	tess.dlightBits |= dlightBits;
+	tess.pshadowBits |= pshadowBits;
+
+	tess.numVertexes += numVerts;
+}
+
+static qboolean RB_SurfaceHelperVBO(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndexes, int firstIndex, int dlightBits, int pshadowBits, qboolean shaderCheck)
+{
+	int i, mergeForward, mergeBack;
+	GLvoid *firstIndexOffset, *lastIndexOffset;
+
+	if (!vbo || !ibo)
+	{
+		return qfalse;
+	}
+
+	if (shaderCheck && !(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal))
+	{
+		return qfalse;
+	}
+
+	RB_CheckVBOandIBO(vbo, ibo);
+
+	tess.dlightBits |= dlightBits;
+	tess.pshadowBits |= pshadowBits;
+
+	// merge this into any existing multidraw primitives
+	mergeForward = -1;
+	mergeBack = -1;
+	firstIndexOffset = BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE));
+	lastIndexOffset  = BUFFER_OFFSET((firstIndex + numIndexes) * sizeof(GL_INDEX_TYPE));
+
+	if (r_mergeMultidraws->integer)
+	{
+		i = 0;
+
+		if (r_mergeMultidraws->integer == 1)
+		{
+			// lazy merge, only check the last primitive
+			if (tess.multiDrawPrimitives)
+			{
+				i = tess.multiDrawPrimitives - 1;
+			}
+		}
+
+		for (; i < tess.multiDrawPrimitives; i++)
+		{
+			if (tess.multiDrawLastIndex[i] == firstIndexOffset)
+			{
+				mergeBack = i;
+			}
+
+			if (lastIndexOffset == tess.multiDrawFirstIndex[i])
+			{
+				mergeForward = i;
+			}
+		}
+	}
+
+	if (mergeBack != -1 && mergeForward == -1)
+	{
+		tess.multiDrawNumIndexes[mergeBack] += numIndexes;
+		tess.multiDrawLastIndex[mergeBack]   = (byte *)tess.multiDrawFirstIndex[mergeBack] + tess.multiDrawNumIndexes[mergeBack] * sizeof(GL_INDEX_TYPE);
+		backEnd.pc.c_multidrawsMerged++;
+	}
+	else if (mergeBack == -1 && mergeForward != -1)
+	{
+		tess.multiDrawNumIndexes[mergeForward] += numIndexes;
+		tess.multiDrawFirstIndex[mergeForward]  = firstIndexOffset;
+		tess.multiDrawLastIndex[mergeForward]   = (byte *)tess.multiDrawFirstIndex[mergeForward] + tess.multiDrawNumIndexes[mergeForward] * sizeof(GL_INDEX_TYPE);
+		backEnd.pc.c_multidrawsMerged++;
+	}
+	else if (mergeBack != -1 && mergeForward != -1)
+	{
+		tess.multiDrawNumIndexes[mergeBack] += numIndexes + tess.multiDrawNumIndexes[mergeForward];
+		tess.multiDrawLastIndex[mergeBack]   = (byte *)tess.multiDrawFirstIndex[mergeBack] + tess.multiDrawNumIndexes[mergeBack] * sizeof(GL_INDEX_TYPE);
+		tess.multiDrawPrimitives--;
+
+		if (mergeForward != tess.multiDrawPrimitives)
+		{
+			tess.multiDrawNumIndexes[mergeForward] = tess.multiDrawNumIndexes[tess.multiDrawPrimitives];
+			tess.multiDrawFirstIndex[mergeForward] = tess.multiDrawFirstIndex[tess.multiDrawPrimitives];
+		}
+		backEnd.pc.c_multidrawsMerged += 2;
+	}
+	else if (mergeBack == -1 && mergeForward == -1)
+	{
+		tess.multiDrawNumIndexes[tess.multiDrawPrimitives] = numIndexes;
+		tess.multiDrawFirstIndex[tess.multiDrawPrimitives] = firstIndexOffset;
+		tess.multiDrawLastIndex[tess.multiDrawPrimitives] = lastIndexOffset;
+		tess.multiDrawPrimitives++;
+	}
+
+	backEnd.pc.c_multidraws++;
+
+	tess.numIndexes  += numIndexes;
+	tess.numVertexes += numVerts;
+
+	return qtrue;
+}
+
+/*
+=============
+RB_SurfaceTriangles
+=============
+*/
+static void RB_SurfaceTriangles( srfTriangles_t *srf ) {
+	if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) )
+	{
+		return;
+	}
+
+	RB_SurfaceHelper(srf->numVerts, srf->verts, srf->numTriangles, srf->triangles, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame]);
+}
+
+
+
+/*
+==============
+RB_SurfaceBeam
+==============
+*/
+static void RB_SurfaceBeam( void )
+{
+#define NUM_BEAM_SEGS 6
+	refEntity_t *e;
+	int	i;
+	vec3_t perpvec;
+	vec3_t direction, normalized_direction;
+	vec3_t	start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS];
+	vec3_t oldorigin, origin;
+
+	e = &backEnd.currentEntity->e;
+
+	oldorigin[0] = e->oldorigin[0];
+	oldorigin[1] = e->oldorigin[1];
+	oldorigin[2] = e->oldorigin[2];
+
+	origin[0] = e->origin[0];
+	origin[1] = e->origin[1];
+	origin[2] = e->origin[2];
+
+	normalized_direction[0] = direction[0] = oldorigin[0] - origin[0];
+	normalized_direction[1] = direction[1] = oldorigin[1] - origin[1];
+	normalized_direction[2] = direction[2] = oldorigin[2] - origin[2];
+
+	if ( VectorNormalize( normalized_direction ) == 0 )
+		return;
+
+	PerpendicularVector( perpvec, normalized_direction );
+
+	VectorScale( perpvec, 4, perpvec );
+
+	for ( i = 0; i < NUM_BEAM_SEGS ; i++ )
+	{
+		RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i );
+//		VectorAdd( start_points[i], origin, start_points[i] );
+		VectorAdd( start_points[i], direction, end_points[i] );
+	}
+
+	GL_Bind( tr.whiteImage );
+
+	GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+	// FIXME: Quake3 doesn't use this, so I never tested it
+	tess.numVertexes = 0;
+	tess.numIndexes = 0;
+	tess.firstIndex = 0;
+
+	for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) {
+		VectorCopy(start_points[ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]);
+		VectorCopy(end_points  [ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]);
+	}
+
+	for ( i = 0; i < NUM_BEAM_SEGS; i++ ) {
+		tess.indexes[tess.numIndexes++] =       i      * 2;
+		tess.indexes[tess.numIndexes++] =      (i + 1) * 2;
+		tess.indexes[tess.numIndexes++] = 1  +  i      * 2;
+
+		tess.indexes[tess.numIndexes++] = 1  +  i      * 2;
+		tess.indexes[tess.numIndexes++] =      (i + 1) * 2;
+		tess.indexes[tess.numIndexes++] = 1  + (i + 1) * 2;
+	}
+
+	// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
+	RB_UpdateVBOs(ATTR_POSITION);
+	
+	{
+		shaderProgram_t *sp = &tr.textureColorShader;
+		vec4_t color;
+
+		GLSL_VertexAttribsState(ATTR_POSITION);
+		GLSL_BindProgram(sp);
+		
+		GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+					
+		color[0] = 1.0f;
+		color[1] = 0.0f;
+		color[2] = 0.0f;
+		color[3] = 1.0f;
+		GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color);
+	}
+
+	R_DrawElementsVBO(tess.numIndexes, tess.firstIndex);
+
+	tess.numIndexes = 0;
+	tess.numVertexes = 0;
+	tess.firstIndex = 0;
+}
+
+//================================================================================
+
+static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth )
+{
+	float		spanWidth2;
+	int			vbase;
+	float		t = len / 256.0f;
+
+	vbase = tess.numVertexes;
+
+	spanWidth2 = -spanWidth;
+
+	// FIXME: use quad stamp?
+	VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] );
+	tess.texCoords[tess.numVertexes][0][0] = 0;
+	tess.texCoords[tess.numVertexes][0][1] = 0;
+	tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25 / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25 / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25 / 255.0f;
+	tess.numVertexes++;
+
+	VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] );
+	tess.texCoords[tess.numVertexes][0][0] = 0;
+	tess.texCoords[tess.numVertexes][0][1] = 1;
+	tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f;
+	tess.numVertexes++;
+
+	VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] );
+
+	tess.texCoords[tess.numVertexes][0][0] = t;
+	tess.texCoords[tess.numVertexes][0][1] = 0;
+	tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f;
+	tess.numVertexes++;
+
+	VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] );
+	tess.texCoords[tess.numVertexes][0][0] = t;
+	tess.texCoords[tess.numVertexes][0][1] = 1;
+	tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f;
+	tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f;
+	tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f;
+	tess.numVertexes++;
+
+	tess.indexes[tess.numIndexes++] = vbase;
+	tess.indexes[tess.numIndexes++] = vbase + 1;
+	tess.indexes[tess.numIndexes++] = vbase + 2;
+
+	tess.indexes[tess.numIndexes++] = vbase + 2;
+	tess.indexes[tess.numIndexes++] = vbase + 1;
+	tess.indexes[tess.numIndexes++] = vbase + 3;
+}
+
+static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up )
+{
+	int i;
+	vec3_t	pos[4];
+	vec3_t	v;
+	int		spanWidth = r_railWidth->integer;
+	float c, s;
+	float		scale;
+
+	if ( numSegs > 1 )
+		numSegs--;
+	if ( !numSegs )
+		return;
+
+	scale = 0.25;
+
+	for ( i = 0; i < 4; i++ )
+	{
+		c = cos( DEG2RAD( 45 + i * 90 ) );
+		s = sin( DEG2RAD( 45 + i * 90 ) );
+		v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth;
+		v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth;
+		v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth;
+		VectorAdd( start, v, pos[i] );
+
+		if ( numSegs > 1 )
+		{
+			// offset by 1 segment if we're doing a long distance shot
+			VectorAdd( pos[i], dir, pos[i] );
+		}
+	}
+
+	for ( i = 0; i < numSegs; i++ )
+	{
+		int j;
+
+		RB_CHECKOVERFLOW( 4, 6 );
+
+		for ( j = 0; j < 4; j++ )
+		{
+			VectorCopy( pos[j], tess.xyz[tess.numVertexes] );
+			tess.texCoords[tess.numVertexes][0][0] = ( j < 2 );
+			tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 );
+			tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f;
+			tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f;
+			tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f;
+			tess.numVertexes++;
+
+			VectorAdd( pos[j], dir, pos[j] );
+		}
+
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0;
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1;
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3;
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3;
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1;
+		tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2;
+	}
+}
+
+/*
+** RB_SurfaceRailRinges
+*/
+static void RB_SurfaceRailRings( void ) {
+	refEntity_t *e;
+	int			numSegs;
+	int			len;
+	vec3_t		vec;
+	vec3_t		right, up;
+	vec3_t		start, end;
+
+	e = &backEnd.currentEntity->e;
+
+	VectorCopy( e->oldorigin, start );
+	VectorCopy( e->origin, end );
+
+	// compute variables
+	VectorSubtract( end, start, vec );
+	len = VectorNormalize( vec );
+	MakeNormalVectors( vec, right, up );
+	numSegs = ( len ) / r_railSegmentLength->value;
+	if ( numSegs <= 0 ) {
+		numSegs = 1;
+	}
+
+	VectorScale( vec, r_railSegmentLength->value, vec );
+
+	DoRailDiscs( numSegs, start, vec, right, up );
+}
+
+/*
+** RB_SurfaceRailCore
+*/
+static void RB_SurfaceRailCore( void ) {
+	refEntity_t *e;
+	int			len;
+	vec3_t		right;
+	vec3_t		vec;
+	vec3_t		start, end;
+	vec3_t		v1, v2;
+
+	e = &backEnd.currentEntity->e;
+
+	VectorCopy( e->oldorigin, start );
+	VectorCopy( e->origin, end );
+
+	VectorSubtract( end, start, vec );
+	len = VectorNormalize( vec );
+
+	// compute side vector
+	VectorSubtract( start, backEnd.viewParms.or.origin, v1 );
+	VectorNormalize( v1 );
+	VectorSubtract( end, backEnd.viewParms.or.origin, v2 );
+	VectorNormalize( v2 );
+	CrossProduct( v1, v2, right );
+	VectorNormalize( right );
+
+	DoRailCore( start, end, right, len, r_railCoreWidth->integer );
+}
+
+/*
+** RB_SurfaceLightningBolt
+*/
+static void RB_SurfaceLightningBolt( void ) {
+	refEntity_t *e;
+	int			len;
+	vec3_t		right;
+	vec3_t		vec;
+	vec3_t		start, end;
+	vec3_t		v1, v2;
+	int			i;
+
+	e = &backEnd.currentEntity->e;
+
+	VectorCopy( e->oldorigin, end );
+	VectorCopy( e->origin, start );
+
+	// compute variables
+	VectorSubtract( end, start, vec );
+	len = VectorNormalize( vec );
+
+	// compute side vector
+	VectorSubtract( start, backEnd.viewParms.or.origin, v1 );
+	VectorNormalize( v1 );
+	VectorSubtract( end, backEnd.viewParms.or.origin, v2 );
+	VectorNormalize( v2 );
+	CrossProduct( v1, v2, right );
+	VectorNormalize( right );
+
+	for ( i = 0 ; i < 4 ; i++ ) {
+		vec3_t	temp;
+
+		DoRailCore( start, end, right, len, 8 );
+		RotatePointAroundVector( temp, vec, right, 45 );
+		VectorCopy( temp, right );
+	}
+}
+
+/*
+** VectorArrayNormalize
+*
+* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0)
+* This means that we don't have to worry about zero length or enormously long vectors.
+*/
+static void VectorArrayNormalize(vec4_t *normals, unsigned int count)
+{
+//    assert(count);
+        
+#if idppc
+    {
+        register float half = 0.5;
+        register float one  = 1.0;
+        float *components = (float *)normals;
+        
+        // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction,
+        // runs *much* faster than calling sqrt().  We'll use a single Newton-Raphson
+        // refinement step to get a little more precision.  This seems to yeild results
+        // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5).
+        // (That is, for the given input range of about 0.6 to 2.0).
+        do {
+            float x, y, z;
+            float B, y0, y1;
+            
+            x = components[0];
+            y = components[1];
+            z = components[2];
+            components += 4;
+            B = x*x + y*y + z*z;
+
+#ifdef __GNUC__            
+            asm("frsqrte %0,%1" : "=f" (y0) : "f" (B));
+#else
+			y0 = __frsqrte(B);
+#endif
+            y1 = y0 + half*y0*(one - B*y0*y0);
+
+            x = x * y1;
+            y = y * y1;
+            components[-4] = x;
+            z = z * y1;
+            components[-3] = y;
+            components[-2] = z;
+        } while(count--);
+    }
+#else // No assembly version for this architecture, or C_ONLY defined
+	// given the input, it's safe to call VectorNormalizeFast
+    while (count--) {
+        VectorNormalizeFast(normals[0]);
+        normals++;
+    }
+#endif
+
+}
+
+
+
+/*
+** LerpMeshVertexes
+*/
+#if idppc_altivec
+static void LerpMeshVertexes_altivec(md3Surface_t *surf, float backlerp)
+{
+	short	*oldXyz, *newXyz, *oldNormals, *newNormals;
+	float	*outXyz, *outNormal;
+	float	oldXyzScale QALIGN(16);
+	float   newXyzScale QALIGN(16);
+	float	oldNormalScale QALIGN(16);
+	float newNormalScale QALIGN(16);
+	int		vertNum;
+	unsigned lat, lng;
+	int		numVerts;
+
+	outXyz = tess.xyz[tess.numVertexes];
+	outNormal = tess.normal[tess.numVertexes];
+
+	newXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+		+ (backEnd.currentEntity->e.frame * surf->numVerts * 4);
+	newNormals = newXyz + 3;
+
+	newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp);
+	newNormalScale = 1.0 - backlerp;
+
+	numVerts = surf->numVerts;
+
+	if ( backlerp == 0 ) {
+		vector signed short newNormalsVec0;
+		vector signed short newNormalsVec1;
+		vector signed int newNormalsIntVec;
+		vector float newNormalsFloatVec;
+		vector float newXyzScaleVec;
+		vector unsigned char newNormalsLoadPermute;
+		vector unsigned char newNormalsStorePermute;
+		vector float zero;
+		
+		newNormalsStorePermute = vec_lvsl(0,(float *)&newXyzScaleVec);
+		newXyzScaleVec = *(vector float *)&newXyzScale;
+		newXyzScaleVec = vec_perm(newXyzScaleVec,newXyzScaleVec,newNormalsStorePermute);
+		newXyzScaleVec = vec_splat(newXyzScaleVec,0);		
+		newNormalsLoadPermute = vec_lvsl(0,newXyz);
+		newNormalsStorePermute = vec_lvsr(0,outXyz);
+		zero = (vector float)vec_splat_s8(0);
+		//
+		// just copy the vertexes
+		//
+		for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+			newXyz += 4, newNormals += 4,
+			outXyz += 4, outNormal += 4) 
+		{
+			newNormalsLoadPermute = vec_lvsl(0,newXyz);
+			newNormalsStorePermute = vec_lvsr(0,outXyz);
+			newNormalsVec0 = vec_ld(0,newXyz);
+			newNormalsVec1 = vec_ld(16,newXyz);
+			newNormalsVec0 = vec_perm(newNormalsVec0,newNormalsVec1,newNormalsLoadPermute);
+			newNormalsIntVec = vec_unpackh(newNormalsVec0);
+			newNormalsFloatVec = vec_ctf(newNormalsIntVec,0);
+			newNormalsFloatVec = vec_madd(newNormalsFloatVec,newXyzScaleVec,zero);
+			newNormalsFloatVec = vec_perm(newNormalsFloatVec,newNormalsFloatVec,newNormalsStorePermute);
+			//outXyz[0] = newXyz[0] * newXyzScale;
+			//outXyz[1] = newXyz[1] * newXyzScale;
+			//outXyz[2] = newXyz[2] * newXyzScale;
+
+			lat = ( newNormals[0] >> 8 ) & 0xff;
+			lng = ( newNormals[0] & 0xff );
+			lat *= (FUNCTABLE_SIZE/256);
+			lng *= (FUNCTABLE_SIZE/256);
+
+			// decode X as cos( lat ) * sin( long )
+			// decode Y as sin( lat ) * sin( long )
+			// decode Z as cos( long )
+
+			outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+			vec_ste(newNormalsFloatVec,0,outXyz);
+			vec_ste(newNormalsFloatVec,4,outXyz);
+			vec_ste(newNormalsFloatVec,8,outXyz);
+		}
+	} else {
+		//
+		// interpolate and copy the vertex and normal
+		//
+		oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+			+ (backEnd.currentEntity->e.oldframe * surf->numVerts * 4);
+		oldNormals = oldXyz + 3;
+
+		oldXyzScale = MD3_XYZ_SCALE * backlerp;
+		oldNormalScale = backlerp;
+
+		for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+			oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4,
+			outXyz += 4, outNormal += 4) 
+		{
+			vec3_t uncompressedOldNormal, uncompressedNewNormal;
+
+			// interpolate the xyz
+			outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale;
+			outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale;
+			outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale;
+
+			// FIXME: interpolate lat/long instead?
+			lat = ( newNormals[0] >> 8 ) & 0xff;
+			lng = ( newNormals[0] & 0xff );
+			lat *= 4;
+			lng *= 4;
+			uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+			lat = ( oldNormals[0] >> 8 ) & 0xff;
+			lng = ( oldNormals[0] & 0xff );
+			lat *= 4;
+			lng *= 4;
+
+			uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+			outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale;
+			outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale;
+			outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale;
+
+//			VectorNormalize (outNormal);
+		}
+    	VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts);
+   	}
+}
+#endif
+
+static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp)
+{
+#if 0
+	short	*oldXyz, *newXyz, *oldNormals, *newNormals;
+	float	*outXyz, *outNormal;
+	float	oldXyzScale, newXyzScale;
+	float	oldNormalScale, newNormalScale;
+	int		vertNum;
+	unsigned lat, lng;
+	int		numVerts;
+
+	outXyz = tess.xyz[tess.numVertexes];
+	outNormal = tess.normal[tess.numVertexes];
+
+	newXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+		+ (backEnd.currentEntity->e.frame * surf->numVerts * 4);
+	newNormals = newXyz + 3;
+
+	newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp);
+	newNormalScale = 1.0 - backlerp;
+
+	numVerts = surf->numVerts;
+
+	if ( backlerp == 0 ) {
+		//
+		// just copy the vertexes
+		//
+		for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+			newXyz += 4, newNormals += 4,
+			outXyz += 4, outNormal += 4) 
+		{
+
+			outXyz[0] = newXyz[0] * newXyzScale;
+			outXyz[1] = newXyz[1] * newXyzScale;
+			outXyz[2] = newXyz[2] * newXyzScale;
+
+			lat = ( newNormals[0] >> 8 ) & 0xff;
+			lng = ( newNormals[0] & 0xff );
+			lat *= (FUNCTABLE_SIZE/256);
+			lng *= (FUNCTABLE_SIZE/256);
+
+			// decode X as cos( lat ) * sin( long )
+			// decode Y as sin( lat ) * sin( long )
+			// decode Z as cos( long )
+
+			outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+		}
+	} else {
+		//
+		// interpolate and copy the vertex and normal
+		//
+		oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+			+ (backEnd.currentEntity->e.oldframe * surf->numVerts * 4);
+		oldNormals = oldXyz + 3;
+
+		oldXyzScale = MD3_XYZ_SCALE * backlerp;
+		oldNormalScale = backlerp;
+
+		for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+			oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4,
+			outXyz += 4, outNormal += 4) 
+		{
+			vec3_t uncompressedOldNormal, uncompressedNewNormal;
+
+			// interpolate the xyz
+			outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale;
+			outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale;
+			outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale;
+
+			// FIXME: interpolate lat/long instead?
+			lat = ( newNormals[0] >> 8 ) & 0xff;
+			lng = ( newNormals[0] & 0xff );
+			lat *= 4;
+			lng *= 4;
+			uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+			lat = ( oldNormals[0] >> 8 ) & 0xff;
+			lng = ( oldNormals[0] & 0xff );
+			lat *= 4;
+			lng *= 4;
+
+			uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+			uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+			uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+			outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale;
+			outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale;
+			outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale;
+
+//			VectorNormalize (outNormal);
+		}
+    	VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts);
+   	}
+#endif
+	float *outXyz, *outNormal;
+	mdvVertex_t *newVerts;
+	int		vertNum;
+
+	newVerts = surf->verts + backEnd.currentEntity->e.frame * surf->numVerts;
+
+	outXyz =    tess.xyz[tess.numVertexes];
+	outNormal = tess.normal[tess.numVertexes];
+
+	if (backlerp == 0)
+	{
+		//
+		// just copy the vertexes
+		//
+
+		for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++)
+		{
+			VectorCopy(newVerts->xyz,    outXyz);
+			VectorCopy(newVerts->normal, outNormal);
+			newVerts++;
+			outXyz += 4;
+			outNormal += 4;
+		}
+	}
+	else
+	{
+		//
+		// interpolate and copy the vertex and normal
+		//
+
+		mdvVertex_t *oldVerts;
+
+		oldVerts = surf->verts + backEnd.currentEntity->e.oldframe * surf->numVerts;
+
+		for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++)
+		{
+			VectorLerp(newVerts->xyz,    oldVerts->xyz,    backlerp, outXyz);
+			VectorLerp(newVerts->normal, oldVerts->normal, backlerp, outNormal);
+			//VectorNormalize(outNormal);
+			newVerts++;
+			oldVerts++;
+			outXyz += 4;
+			outNormal += 4;
+		}
+		VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], surf->numVerts);
+	}
+
+}
+
+static void LerpMeshVertexes(mdvSurface_t *surf, float backlerp)
+{
+#if 0
+#if idppc_altivec
+	if (com_altivec->integer) {
+		// must be in a seperate function or G3 systems will crash.
+		LerpMeshVertexes_altivec( surf, backlerp );
+		return;
+	}
+#endif // idppc_altivec
+#endif
+	LerpMeshVertexes_scalar( surf, backlerp );
+}
+
+
+/*
+=============
+RB_SurfaceMesh
+=============
+*/
+static void RB_SurfaceMesh(mdvSurface_t *surface) {
+	int				j;
+	float			backlerp;
+	srfTriangle_t 	*triangles;
+	mdvSt_t			*texCoords;
+	int				indexes;
+	int				Bob, Doug;
+	int				numVerts;
+
+	if (  backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) {
+		backlerp = 0;
+	} else  {
+		backlerp = backEnd.currentEntity->e.backlerp;
+	}
+
+	RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 );
+
+	LerpMeshVertexes (surface, backlerp);
+
+	triangles = surface->triangles;
+	indexes = surface->numTriangles * 3;
+	Bob = tess.numIndexes;
+	Doug = tess.numVertexes;
+	for (j = 0 ; j < surface->numTriangles ; j++) {
+		tess.indexes[Bob + j*3 + 0] = Doug + triangles[j].indexes[0];
+		tess.indexes[Bob + j*3 + 1] = Doug + triangles[j].indexes[1];
+		tess.indexes[Bob + j*3 + 2] = Doug + triangles[j].indexes[2];
+	}
+	tess.numIndexes += indexes;
+
+	texCoords = surface->st;
+
+	numVerts = surface->numVerts;
+	for ( j = 0; j < numVerts; j++ ) {
+		tess.texCoords[Doug + j][0][0] = texCoords[j].st[0];
+		tess.texCoords[Doug + j][0][1] = texCoords[j].st[1];
+		// FIXME: fill in lightmapST for completeness?
+	}
+
+	tess.numVertexes += surface->numVerts;
+
+}
+
+
+/*
+==============
+RB_SurfaceFace
+==============
+*/
+static void RB_SurfaceFace( srfSurfaceFace_t *srf ) {
+	if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) )
+	{
+		return;
+	}
+
+	RB_SurfaceHelper(srf->numVerts, srf->verts, srf->numTriangles, srf->triangles, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame]);
+}
+
+
+static float	LodErrorForVolume( vec3_t local, float radius ) {
+	vec3_t		world;
+	float		d;
+
+	// never let it go negative
+	if ( r_lodCurveError->value < 0 ) {
+		return 0;
+	}
+
+	world[0] = local[0] * backEnd.or.axis[0][0] + local[1] * backEnd.or.axis[1][0] + 
+		local[2] * backEnd.or.axis[2][0] + backEnd.or.origin[0];
+	world[1] = local[0] * backEnd.or.axis[0][1] + local[1] * backEnd.or.axis[1][1] + 
+		local[2] * backEnd.or.axis[2][1] + backEnd.or.origin[1];
+	world[2] = local[0] * backEnd.or.axis[0][2] + local[1] * backEnd.or.axis[1][2] + 
+		local[2] * backEnd.or.axis[2][2] + backEnd.or.origin[2];
+
+	VectorSubtract( world, backEnd.viewParms.or.origin, world );
+	d = DotProduct( world, backEnd.viewParms.or.axis[0] );
+
+	if ( d < 0 ) {
+		d = -d;
+	}
+	d -= radius;
+	if ( d < 1 ) {
+		d = 1;
+	}
+
+	return r_lodCurveError->value / d;
+}
+
+/*
+=============
+RB_SurfaceGrid
+
+Just copy the grid of points and triangulate
+=============
+*/
+static void RB_SurfaceGrid( srfGridMesh_t *srf ) {
+	int		i, j;
+	float	*xyz;
+	float	*texCoords, *lightCoords;
+	float	*normal;
+#ifdef USE_VERT_TANGENT_SPACE
+	float   *tangent, *bitangent;
+#endif
+	float   *color, *lightdir;
+	srfVert_t	*dv;
+	int		rows, irows, vrows;
+	int		used;
+	int		widthTable[MAX_GRID_SIZE];
+	int		heightTable[MAX_GRID_SIZE];
+	float	lodError;
+	int		lodWidth, lodHeight;
+	int		numVertexes;
+	int		dlightBits;
+	int     pshadowBits;
+	//int		*vDlightBits;
+
+	if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) )
+	{
+		return;
+	}
+
+	dlightBits = srf->dlightBits[backEnd.smpFrame];
+	tess.dlightBits |= dlightBits;
+
+	pshadowBits = srf->pshadowBits[backEnd.smpFrame];
+	tess.pshadowBits |= pshadowBits;
+
+	// determine the allowable discrepance
+	lodError = LodErrorForVolume( srf->lodOrigin, srf->lodRadius );
+
+	// determine which rows and columns of the subdivision
+	// we are actually going to use
+	widthTable[0] = 0;
+	lodWidth = 1;
+	for ( i = 1 ; i < srf->width-1 ; i++ ) {
+		if ( srf->widthLodError[i] <= lodError ) {
+			widthTable[lodWidth] = i;
+			lodWidth++;
+		}
+	}
+	widthTable[lodWidth] = srf->width-1;
+	lodWidth++;
+
+	heightTable[0] = 0;
+	lodHeight = 1;
+	for ( i = 1 ; i < srf->height-1 ; i++ ) {
+		if ( srf->heightLodError[i] <= lodError ) {
+			heightTable[lodHeight] = i;
+			lodHeight++;
+		}
+	}
+	heightTable[lodHeight] = srf->height-1;
+	lodHeight++;
+
+
+	// very large grids may have more points or indexes than can be fit
+	// in the tess structure, so we may have to issue it in multiple passes
+
+	used = 0;
+	while ( used < lodHeight - 1 ) {
+		// see how many rows of both verts and indexes we can add without overflowing
+		do {
+			vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth;
+			irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 );
+
+			// if we don't have enough space for at least one strip, flush the buffer
+			if ( vrows < 2 || irows < 1 ) {
+				RB_EndSurface();
+				RB_BeginSurface(tess.shader, tess.fogNum );
+			} else {
+				break;
+			}
+		} while ( 1 );
+		
+		rows = irows;
+		if ( vrows < irows + 1 ) {
+			rows = vrows - 1;
+		}
+		if ( used + rows > lodHeight ) {
+			rows = lodHeight - used;
+		}
+
+		numVertexes = tess.numVertexes;
+
+		xyz = tess.xyz[numVertexes];
+		normal = tess.normal[numVertexes];
+#ifdef USE_VERT_TANGENT_SPACE
+		tangent = tess.tangent[numVertexes];
+		bitangent = tess.bitangent[numVertexes];
+#endif
+		texCoords = tess.texCoords[numVertexes][0];
+		lightCoords = tess.texCoords[numVertexes][1];
+		color = tess.vertexColors[numVertexes];
+		lightdir = tess.lightdir[numVertexes];
+		//vDlightBits = &tess.vertexDlightBits[numVertexes];
+
+		for ( i = 0 ; i < rows ; i++ ) {
+			for ( j = 0 ; j < lodWidth ; j++ ) {
+				dv = srf->verts + heightTable[ used + i ] * srf->width
+					+ widthTable[ j ];
+
+				if ( tess.shader->vertexAttribs & ATTR_POSITION )
+				{
+					VectorCopy(dv->xyz, xyz);
+					xyz += 4;
+				}
+
+				if ( tess.shader->vertexAttribs & ATTR_NORMAL )
+				{
+					VectorCopy(dv->normal, normal);
+					normal += 4;
+				}
+
+#ifdef USE_VERT_TANGENT_SPACE
+				if ( tess.shader->vertexAttribs & ATTR_TANGENT )
+				{
+					VectorCopy(dv->tangent, tangent);
+					tangent += 4;
+				}
+
+				if ( tess.shader->vertexAttribs & ATTR_BITANGENT )
+				{
+					VectorCopy(dv->bitangent, bitangent);
+					bitangent += 4;
+				}
+#endif
+				if ( tess.shader->vertexAttribs & ATTR_TEXCOORD )
+				{
+					VectorCopy2(dv->st, texCoords);
+					texCoords += 4;
+				}
+
+				if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD )
+				{
+					VectorCopy2(dv->lightmap, lightCoords);
+					lightCoords += 4;
+				}
+
+				if ( tess.shader->vertexAttribs & ATTR_COLOR )
+				{
+					VectorCopy4(dv->vertexColors, color);
+					color += 4;
+				}
+
+				if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
+				{
+					VectorCopy(dv->lightdir, lightdir);
+					lightdir += 4;
+				}
+
+				//*vDlightBits++ = dlightBits;
+			}
+		}
+
+
+		// add the indexes
+		{
+			int		numIndexes;
+			int		w, h;
+
+			h = rows - 1;
+			w = lodWidth - 1;
+			numIndexes = tess.numIndexes;
+			for (i = 0 ; i < h ; i++) {
+				for (j = 0 ; j < w ; j++) {
+					int		v1, v2, v3, v4;
+			
+					// vertex order to be reckognized as tristrips
+					v1 = numVertexes + i*lodWidth + j + 1;
+					v2 = v1 - 1;
+					v3 = v2 + lodWidth;
+					v4 = v3 + 1;
+
+					tess.indexes[numIndexes] = v2;
+					tess.indexes[numIndexes+1] = v3;
+					tess.indexes[numIndexes+2] = v1;
+					
+					tess.indexes[numIndexes+3] = v1;
+					tess.indexes[numIndexes+4] = v3;
+					tess.indexes[numIndexes+5] = v4;
+					numIndexes += 6;
+				}
+			}
+
+			tess.numIndexes = numIndexes;
+		}
+
+		tess.numVertexes += rows * lodWidth;
+
+		used += rows - 1;
+	}
+}
+
+
+/*
+===========================================================================
+
+NULL MODEL
+
+===========================================================================
+*/
+
+/*
+===================
+RB_SurfaceAxis
+
+Draws x/y/z lines from the origin for orientation debugging
+===================
+*/
+static void RB_SurfaceAxis( void ) {
+	// FIXME: implement this
+#if 0
+	GL_Bind( tr.whiteImage );
+	qglLineWidth( 3 );
+	qglBegin( GL_LINES );
+	qglColor3f( 1,0,0 );
+	qglVertex3f( 0,0,0 );
+	qglVertex3f( 16,0,0 );
+	qglColor3f( 0,1,0 );
+	qglVertex3f( 0,0,0 );
+	qglVertex3f( 0,16,0 );
+	qglColor3f( 0,0,1 );
+	qglVertex3f( 0,0,0 );
+	qglVertex3f( 0,0,16 );
+	qglEnd();
+	qglLineWidth( 1 );
+#endif
+}
+
+//===========================================================================
+
+/*
+====================
+RB_SurfaceEntity
+
+Entities that have a single procedurally generated surface
+====================
+*/
+static void RB_SurfaceEntity( surfaceType_t *surfType ) {
+	switch( backEnd.currentEntity->e.reType ) {
+	case RT_SPRITE:
+		RB_SurfaceSprite();
+		break;
+	case RT_BEAM:
+		RB_SurfaceBeam();
+		break;
+	case RT_RAIL_CORE:
+		RB_SurfaceRailCore();
+		break;
+	case RT_RAIL_RINGS:
+		RB_SurfaceRailRings();
+		break;
+	case RT_LIGHTNING:
+		RB_SurfaceLightningBolt();
+		break;
+	default:
+		RB_SurfaceAxis();
+		break;
+	}
+	return;
+}
+
+static void RB_SurfaceBad( surfaceType_t *surfType ) {
+	ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" );
+}
+
+static void RB_SurfaceFlare(srfFlare_t *surf)
+{
+	if (r_flares->integer)
+		RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal);
+}
+
+static void RB_SurfaceVBOMesh(srfVBOMesh_t * srf)
+{
+	RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qfalse );
+}
+
+void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface)
+{
+	//mdvModel_t     *mdvModel;
+	//mdvSurface_t   *mdvSurface;
+	refEntity_t    *refEnt;
+
+	GLimp_LogComment("--- RB_SurfaceVBOMDVMesh ---\n");
+
+	if(!surface->vbo || !surface->ibo)
+		return;
+
+	//RB_CheckVBOandIBO(surface->vbo, surface->ibo);
+	RB_EndSurface();
+	RB_BeginSurface(tess.shader, tess.fogNum);
+
+	R_BindVBO(surface->vbo);
+	R_BindIBO(surface->ibo);
+
+	tess.useInternalVBO = qfalse;
+
+	tess.numIndexes += surface->numIndexes;
+	tess.numVertexes += surface->numVerts;
+
+	//mdvModel = surface->mdvModel;
+	//mdvSurface = surface->mdvSurface;
+
+	refEnt = &backEnd.currentEntity->e;
+
+	if(refEnt->oldframe == refEnt->frame)
+	{
+		glState.vertexAttribsInterpolation = 0;
+	}
+	else
+	{
+		glState.vertexAttribsInterpolation = refEnt->backlerp;
+	}
+
+	glState.vertexAttribsOldFrame = refEnt->oldframe;
+	glState.vertexAttribsNewFrame = refEnt->frame;
+
+	RB_EndSurface();
+
+	// So we don't lerp surfaces that shouldn't be lerped
+	glState.vertexAttribsInterpolation = 0;
+}
+
+static void RB_SurfaceDisplayList( srfDisplayList_t *surf ) {
+	// all apropriate state must be set in RB_BeginSurface
+	// this isn't implemented yet...
+	qglCallList( surf->listNum );
+}
+
+static void RB_SurfaceSkip( void *surf ) {
+}
+
+
+void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = {
+	(void(*)(void*))RB_SurfaceBad,			// SF_BAD, 
+	(void(*)(void*))RB_SurfaceSkip,			// SF_SKIP, 
+	(void(*)(void*))RB_SurfaceFace,			// SF_FACE,
+	(void(*)(void*))RB_SurfaceGrid,			// SF_GRID,
+	(void(*)(void*))RB_SurfaceTriangles,		// SF_TRIANGLES,
+	(void(*)(void*))RB_SurfacePolychain,		// SF_POLY,
+	(void(*)(void*))RB_SurfaceMesh,			// SF_MDV,
+	(void(*)(void*))RB_SurfaceAnim,			// SF_MD4,
+#ifdef RAVENMD4
+	(void(*)(void*))RB_MDRSurfaceAnim,		// SF_MDR,
+#endif
+	(void(*)(void*))RB_IQMSurfaceAnim,		// SF_IQM,
+	(void(*)(void*))RB_SurfaceFlare,		// SF_FLARE,
+	(void(*)(void*))RB_SurfaceEntity,		// SF_ENTITY
+	(void(*)(void*))RB_SurfaceDisplayList,		// SF_DISPLAY_LIST
+	(void(*)(void*))RB_SurfaceVBOMesh,	    // SF_VBO_MESH,
+	(void(*)(void*))RB_SurfaceVBOMDVMesh,   // SF_VBO_MDVMESH
+};

Added: trunk/code/rend2/tr_vbo.c
===================================================================
--- trunk/code/rend2/tr_vbo.c	                        (rev 0)
+++ trunk/code/rend2/tr_vbo.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,932 @@
+/*
+===========================================================================
+Copyright (C) 2007-2009 Robert Beckebans <trebor_7 at users.sourceforge.net>
+
+This file is part of XreaL source code.
+
+XreaL source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+XreaL source code 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 XreaL source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+// tr_vbo.c
+#include "tr_local.h"
+
+/*
+============
+R_CreateVBO
+============
+*/
+VBO_t          *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage)
+{
+	VBO_t          *vbo;
+	int				glUsage;
+
+	switch (usage)
+	{
+		case VBO_USAGE_STATIC:
+			glUsage = GL_STATIC_DRAW_ARB;
+			break;
+
+		case VBO_USAGE_DYNAMIC:
+			glUsage = GL_DYNAMIC_DRAW_ARB;
+			break;
+
+		default:
+			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			return NULL;
+	}
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "R_CreateVBO: \"%s\" is too long\n", name);
+	}
+
+	if ( tr.numVBOs == MAX_VBOS ) {
+		ri.Error( ERR_DROP, "R_CreateVBO: MAX_VBOS hit\n");
+	}
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
+	tr.numVBOs++;
+
+	memset(vbo, 0, sizeof(*vbo));
+
+	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
+
+	vbo->vertexesSize = vertexesSize;
+
+	qglGenBuffersARB(1, &vbo->vertexesVBO);
+
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
+	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexesSize, vertexes, glUsage);
+
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+
+	glState.currentVBO = NULL;
+
+	GL_CheckErrors();
+
+	return vbo;
+}
+
+/*
+============
+R_CreateVBO2
+============
+*/
+VBO_t          *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * verts, unsigned int stateBits, vboUsage_t usage)
+{
+	VBO_t          *vbo;
+	int             i;
+
+	byte           *data;
+	int             dataSize;
+	int             dataOfs;
+
+	int				glUsage;
+
+	switch (usage)
+	{
+		case VBO_USAGE_STATIC:
+			glUsage = GL_STATIC_DRAW_ARB;
+			break;
+
+		case VBO_USAGE_DYNAMIC:
+			glUsage = GL_DYNAMIC_DRAW_ARB;
+			break;
+
+		default:
+			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			return NULL;
+	}
+
+	if(!numVertexes)
+		return NULL;
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "R_CreateVBO2: \"%s\" is too long\n", name);
+	}
+
+	if ( tr.numVBOs == MAX_VBOS ) {
+		ri.Error( ERR_DROP, "R_CreateVBO2: MAX_VBOS hit\n");
+	}
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low);
+	tr.numVBOs++;
+
+	memset(vbo, 0, sizeof(*vbo));
+
+	Q_strncpyz(vbo->name, name, sizeof(vbo->name));
+
+	if (usage == VBO_USAGE_STATIC)
+	{
+		// since these vertex attributes are never altered, interleave them
+		vbo->ofs_xyz = 0;
+		dataSize = sizeof(verts[0].xyz);
+
+		if(stateBits & ATTR_NORMAL)
+		{
+			vbo->ofs_normal = dataSize;
+			dataSize += sizeof(verts[0].normal);
+		}
+
+#ifdef USE_VERT_TANGENT_SPACE
+		if(stateBits & ATTR_TANGENT)
+		{
+			vbo->ofs_tangent = dataSize;
+			dataSize += sizeof(verts[0].tangent);
+		}
+
+		if(stateBits & ATTR_BITANGENT)
+		{
+			vbo->ofs_bitangent = dataSize;
+			dataSize += sizeof(verts[0].bitangent);
+		}
+#endif
+
+		if(stateBits & ATTR_TEXCOORD)
+		{
+			vbo->ofs_st = dataSize;
+			dataSize += sizeof(verts[0].st);
+		}
+
+		if(stateBits & ATTR_LIGHTCOORD)
+		{
+			vbo->ofs_lightmap = dataSize;
+			dataSize += sizeof(verts[0].lightmap);
+		}
+
+		if(stateBits & ATTR_COLOR)
+		{
+			vbo->ofs_vertexcolor = dataSize;
+			dataSize += sizeof(verts[0].vertexColors);
+		}
+
+		if(stateBits & ATTR_LIGHTDIRECTION)
+		{
+			vbo->ofs_lightdir = dataSize;
+			dataSize += sizeof(verts[0].lightdir);
+		}
+
+		vbo->stride_xyz         = dataSize;
+		vbo->stride_normal      = dataSize;
+#ifdef USE_VERT_TANGENT_SPACE
+		vbo->stride_tangent     = dataSize;
+		vbo->stride_bitangent   = dataSize;
+#endif
+		vbo->stride_st          = dataSize;
+		vbo->stride_lightmap    = dataSize;
+		vbo->stride_vertexcolor = dataSize;
+		vbo->stride_lightdir    = dataSize;
+
+		// create VBO
+		dataSize *= numVertexes;
+		data = ri.Hunk_AllocateTempMemory(dataSize);
+		dataOfs = 0;
+
+		//ri.Printf(PRINT_ALL, "CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
+			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
+
+		for (i = 0; i < numVertexes; i++)
+		{
+			// xyz
+			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
+			dataOfs += sizeof(verts[i].xyz);
+
+			// normal
+			if(stateBits & ATTR_NORMAL)
+			{
+				memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal));
+				dataOfs += sizeof(verts[i].normal);
+			}
+
+#ifdef USE_VERT_TANGENT_SPACE
+			// tangent
+			if(stateBits & ATTR_TANGENT)
+			{
+				memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent));
+				dataOfs += sizeof(verts[i].tangent);
+			}
+
+			// bitangent
+			if(stateBits & ATTR_BITANGENT)
+			{
+				memcpy(data + dataOfs, &verts[i].bitangent, sizeof(verts[i].bitangent));
+				dataOfs += sizeof(verts[i].bitangent);
+			}
+#endif
+
+			// vertex texcoords
+			if(stateBits & ATTR_TEXCOORD)
+			{
+				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
+				dataOfs += sizeof(verts[i].st);
+			}
+
+			// feed vertex lightmap texcoords
+			if(stateBits & ATTR_LIGHTCOORD)
+			{
+				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
+				dataOfs += sizeof(verts[i].lightmap);
+			}
+
+			// feed vertex colors
+			if(stateBits & ATTR_COLOR)
+			{
+				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
+				dataOfs += sizeof(verts[i].vertexColors);
+			}
+
+			// feed vertex light directions
+			if(stateBits & ATTR_LIGHTDIRECTION)
+			{
+				memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir));
+				dataOfs += sizeof(verts[i].lightdir);
+			}
+		}
+	}
+	else
+	{
+		// since these vertex attributes may be changed, put them in flat arrays
+		dataSize = sizeof(verts[0].xyz);
+
+		if(stateBits & ATTR_NORMAL)
+		{
+			dataSize += sizeof(verts[0].normal);
+		}
+
+#ifdef USE_VERT_TANGENT_SPACE
+		if(stateBits & ATTR_TANGENT)
+		{
+			dataSize += sizeof(verts[0].tangent);
+		}
+
+		if(stateBits & ATTR_BITANGENT)
+		{
+			dataSize += sizeof(verts[0].bitangent);
+		}
+#endif
+
+		if(stateBits & ATTR_TEXCOORD)
+		{
+			dataSize += sizeof(verts[0].st);
+		}
+
+		if(stateBits & ATTR_LIGHTCOORD)
+		{
+			dataSize += sizeof(verts[0].lightmap);
+		}
+
+		if(stateBits & ATTR_COLOR)
+		{
+			dataSize += sizeof(verts[0].vertexColors);
+		}
+
+		if(stateBits & ATTR_LIGHTDIRECTION)
+		{
+			dataSize += sizeof(verts[0].lightdir);
+		}
+
+		// create VBO
+		dataSize *= numVertexes;
+		data = ri.Hunk_AllocateTempMemory(dataSize);
+		dataOfs = 0;
+
+		vbo->ofs_xyz            = 0;
+		vbo->ofs_normal         = 0;
+#ifdef USE_VERT_TANGENT_SPACE
+		vbo->ofs_tangent        = 0;
+		vbo->ofs_bitangent      = 0;
+#endif
+		vbo->ofs_st             = 0;
+		vbo->ofs_lightmap       = 0;
+		vbo->ofs_vertexcolor    = 0;
+		vbo->ofs_lightdir       = 0;
+
+		vbo->stride_xyz         = sizeof(verts[0].xyz);
+		vbo->stride_normal      = sizeof(verts[0].normal);
+#ifdef USE_VERT_TANGENT_SPACE
+		vbo->stride_tangent     = sizeof(verts[0].tangent);
+		vbo->stride_bitangent   = sizeof(verts[0].bitangent);
+#endif
+		vbo->stride_vertexcolor = sizeof(verts[0].vertexColors);
+		vbo->stride_st          = sizeof(verts[0].st);
+		vbo->stride_lightmap    = sizeof(verts[0].lightmap);
+		vbo->stride_lightdir    = sizeof(verts[0].lightdir);
+
+		//ri.Printf(PRINT_ALL, "2CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor,
+			//vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor);
+
+		// xyz
+		for (i = 0; i < numVertexes; i++)
+		{
+			memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
+			dataOfs += sizeof(verts[i].xyz);
+		}
+
+		// normal
+		if(stateBits & ATTR_NORMAL)
+		{
+			vbo->ofs_normal = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal));
+				dataOfs += sizeof(verts[i].normal);
+			}
+		}
+
+#ifdef USE_VERT_TANGENT_SPACE
+		// tangent
+		if(stateBits & ATTR_TANGENT)
+		{
+			vbo->ofs_tangent = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent));
+				dataOfs += sizeof(verts[i].tangent);
+			}
+		}
+
+		// bitangent
+		if(stateBits & ATTR_BITANGENT)
+		{
+			vbo->ofs_bitangent = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].bitangent, sizeof(verts[i].bitangent));
+				dataOfs += sizeof(verts[i].bitangent);
+			}
+		}
+#endif
+
+		// vertex texcoords
+		if(stateBits & ATTR_TEXCOORD)
+		{
+			vbo->ofs_st = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
+				dataOfs += sizeof(verts[i].st);
+			}
+		}
+
+		// feed vertex lightmap texcoords
+		if(stateBits & ATTR_LIGHTCOORD)
+		{
+			vbo->ofs_lightmap = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
+				dataOfs += sizeof(verts[i].lightmap);
+			}
+		}
+
+		// feed vertex colors
+		if(stateBits & ATTR_COLOR)
+		{
+			vbo->ofs_vertexcolor = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors));
+				dataOfs += sizeof(verts[i].vertexColors);
+			}
+		}
+
+		// feed vertex lightdirs
+		if(stateBits & ATTR_LIGHTDIRECTION)
+		{
+			vbo->ofs_lightdir = dataOfs;
+			for (i = 0; i < numVertexes; i++)
+			{
+				memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir));
+				dataOfs += sizeof(verts[i].lightdir);
+			}
+		}
+	}
+
+
+	vbo->vertexesSize = dataSize;
+
+	qglGenBuffersARB(1, &vbo->vertexesVBO);
+
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
+	qglBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, data, glUsage);
+
+	qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+
+	glState.currentVBO = NULL;
+
+	GL_CheckErrors();
+
+	ri.Hunk_FreeTempMemory(data);
+
+	return vbo;
+}
+
+
+/*
+============
+R_CreateIBO
+============
+*/
+IBO_t          *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage)
+{
+	IBO_t          *ibo;
+	int				glUsage;
+
+	switch (usage)
+	{
+		case VBO_USAGE_STATIC:
+			glUsage = GL_STATIC_DRAW_ARB;
+			break;
+
+		case VBO_USAGE_DYNAMIC:
+			glUsage = GL_DYNAMIC_DRAW_ARB;
+			break;
+
+		default:
+			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			return NULL;
+	}
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "R_CreateIBO: \"%s\" is too long\n", name);
+	}
+
+	if ( tr.numIBOs == MAX_IBOS ) {
+		ri.Error( ERR_DROP, "R_CreateIBO: MAX_IBOS hit\n");
+	}
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
+	tr.numIBOs++;
+
+	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
+
+	ibo->indexesSize = indexesSize;
+
+	qglGenBuffersARB(1, &ibo->indexesVBO);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+
+	glState.currentIBO = NULL;
+
+	GL_CheckErrors();
+
+	return ibo;
+}
+
+/*
+============
+R_CreateIBO2
+============
+*/
+IBO_t          *R_CreateIBO2(const char *name, int numTriangles, srfTriangle_t * triangles, vboUsage_t usage)
+{
+	IBO_t          *ibo;
+	int             i, j;
+
+	byte           *indexes;
+	int             indexesSize;
+	int             indexesOfs;
+
+	srfTriangle_t  *tri;
+	glIndex_t       index;
+	int				glUsage;
+
+	switch (usage)
+	{
+		case VBO_USAGE_STATIC:
+			glUsage = GL_STATIC_DRAW_ARB;
+			break;
+
+		case VBO_USAGE_DYNAMIC:
+			glUsage = GL_DYNAMIC_DRAW_ARB;
+			break;
+
+		default:
+			Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage);
+			return NULL;
+	}
+
+	if(!numTriangles)
+		return NULL;
+
+	if(strlen(name) >= MAX_QPATH)
+	{
+		ri.Error(ERR_DROP, "R_CreateIBO2: \"%s\" is too long\n", name);
+	}
+
+	if ( tr.numIBOs == MAX_IBOS ) {
+		ri.Error( ERR_DROP, "R_CreateIBO2: MAX_IBOS hit\n");
+	}
+
+	// make sure the render thread is stopped
+	R_SyncRenderThread();
+
+	ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low);
+	tr.numIBOs++;
+
+	Q_strncpyz(ibo->name, name, sizeof(ibo->name));
+
+	indexesSize = numTriangles * 3 * sizeof(int);
+	indexes = ri.Hunk_AllocateTempMemory(indexesSize);
+	indexesOfs = 0;
+
+	for(i = 0, tri = triangles; i < numTriangles; i++, tri++)
+	{
+		for(j = 0; j < 3; j++)
+		{
+			index = tri->indexes[j];
+			memcpy(indexes + indexesOfs, &index, sizeof(glIndex_t));
+			indexesOfs += sizeof(glIndex_t);
+		}
+	}
+
+	ibo->indexesSize = indexesSize;
+
+	qglGenBuffersARB(1, &ibo->indexesVBO);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
+	qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage);
+
+	qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+
+	glState.currentIBO = NULL;
+
+	GL_CheckErrors();
+
+	ri.Hunk_FreeTempMemory(indexes);
+
+	return ibo;
+}
+
+/*
+============
+R_BindVBO
+============
+*/
+void R_BindVBO(VBO_t * vbo)
+{
+	if(!vbo)
+	{
+		//R_BindNullVBO();
+		ri.Error(ERR_DROP, "R_BindNullVBO: NULL vbo");
+		return;
+	}
+
+	if(r_logFile->integer)
+	{
+		// don't just call LogComment, or we will get a call to va() every frame!
+		GLimp_LogComment(va("--- R_BindVBO( %s ) ---\n", vbo->name));
+	}
+
+	if(glState.currentVBO != vbo)
+	{
+		glState.currentVBO = vbo;
+		glState.vertexAttribPointersSet = 0;
+
+		glState.vertexAttribsInterpolation = 0;
+		glState.vertexAttribsOldFrame = 0;
+		glState.vertexAttribsNewFrame = 0;
+
+		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO);
+
+		backEnd.pc.c_vboVertexBuffers++;
+	}
+}
+
+/*
+============
+R_BindNullVBO
+============
+*/
+void R_BindNullVBO(void)
+{
+	GLimp_LogComment("--- R_BindNullVBO ---\n");
+
+	if(glState.currentVBO)
+	{
+		qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
+		glState.currentVBO = NULL;
+	}
+
+	GL_CheckErrors();
+}
+
+/*
+============
+R_BindIBO
+============
+*/
+void R_BindIBO(IBO_t * ibo)
+{
+	if(!ibo)
+	{
+		//R_BindNullIBO();
+		ri.Error(ERR_DROP, "R_BindIBO: NULL ibo");
+		return;
+	}
+
+	if(r_logFile->integer)
+	{
+		// don't just call LogComment, or we will get a call to va() every frame!
+		GLimp_LogComment(va("--- R_BindIBO( %s ) ---\n", ibo->name));
+	}
+
+	if(glState.currentIBO != ibo)
+	{
+		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO);
+
+		glState.currentIBO = ibo;
+
+		backEnd.pc.c_vboIndexBuffers++;
+	}
+}
+
+/*
+============
+R_BindNullIBO
+============
+*/
+void R_BindNullIBO(void)
+{
+	GLimp_LogComment("--- R_BindNullIBO ---\n");
+
+	if(glState.currentIBO)
+	{
+		qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
+		glState.currentIBO = NULL;
+		glState.vertexAttribPointersSet = 0;
+	}
+}
+
+/*
+============
+R_InitVBOs
+============
+*/
+void R_InitVBOs(void)
+{
+	int             dataSize;
+	int             offset;
+
+	ri.Printf(PRINT_ALL, "------- R_InitVBOs -------\n");
+
+	tr.numVBOs = 0;
+	tr.numIBOs = 0;
+
+	dataSize  = sizeof(tess.xyz[0]);
+	dataSize += sizeof(tess.normal[0]);
+#ifdef USE_VERT_TANGENT_SPACE
+	dataSize += sizeof(tess.tangent[0]);
+	dataSize += sizeof(tess.bitangent[0]);
+#endif
+	dataSize += sizeof(tess.vertexColors[0]);
+	dataSize += sizeof(tess.texCoords[0][0]) * 2;
+	dataSize += sizeof(tess.lightdir[0]);
+	dataSize *= SHADER_MAX_VERTEXES;
+
+	tess.vbo = R_CreateVBO("tessVertexArray_VBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+
+	offset = 0;
+
+	tess.vbo->ofs_xyz         = offset; offset += sizeof(tess.xyz[0])              * SHADER_MAX_VERTEXES;
+	tess.vbo->ofs_normal      = offset; offset += sizeof(tess.normal[0])           * SHADER_MAX_VERTEXES;
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.vbo->ofs_tangent     = offset; offset += sizeof(tess.tangent[0])          * SHADER_MAX_VERTEXES;
+	tess.vbo->ofs_bitangent   = offset; offset += sizeof(tess.bitangent[0])        * SHADER_MAX_VERTEXES;
+#endif
+	// these next two are actually interleaved
+	tess.vbo->ofs_st          = offset; 
+	tess.vbo->ofs_lightmap    = offset + sizeof(tess.texCoords[0][0]);
+	                                    offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES;
+
+	tess.vbo->ofs_vertexcolor = offset; offset += sizeof(tess.vertexColors[0])     * SHADER_MAX_VERTEXES;
+	tess.vbo->ofs_lightdir    = offset;
+
+	tess.vbo->stride_xyz         = sizeof(tess.xyz[0]);
+	tess.vbo->stride_normal      = sizeof(tess.normal[0]);
+#ifdef USE_VERT_TANGENT_SPACE
+	tess.vbo->stride_tangent     = sizeof(tess.tangent[0]);
+	tess.vbo->stride_bitangent   = sizeof(tess.bitangent[0]);
+#endif
+	tess.vbo->stride_vertexcolor = sizeof(tess.vertexColors[0]);
+	tess.vbo->stride_st          = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vbo->stride_lightmap    = sizeof(tess.texCoords[0][0]) * 2;
+	tess.vbo->stride_lightdir    = sizeof(tess.lightdir[0]);
+
+	dataSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+
+	tess.ibo = R_CreateIBO("tessVertexArray_IBO", NULL, dataSize, VBO_USAGE_DYNAMIC);
+
+	R_BindNullVBO();
+	R_BindNullIBO();
+
+	GL_CheckErrors();
+}
+
+/*
+============
+R_ShutdownVBOs
+============
+*/
+void R_ShutdownVBOs(void)
+{
+	int             i;
+	VBO_t          *vbo;
+	IBO_t          *ibo;
+
+	ri.Printf(PRINT_ALL, "------- R_ShutdownVBOs -------\n");
+
+	R_BindNullVBO();
+	R_BindNullIBO();
+
+
+	for(i = 0; i < tr.numVBOs; i++)
+	{
+		vbo = tr.vbos[i];
+
+		if(vbo->vertexesVBO)
+		{
+			qglDeleteBuffersARB(1, &vbo->vertexesVBO);
+		}
+
+		//ri.Free(vbo);
+	}
+
+	for(i = 0; i < tr.numIBOs; i++)
+	{
+		ibo = tr.ibos[i];
+
+		if(ibo->indexesVBO)
+		{
+			qglDeleteBuffersARB(1, &ibo->indexesVBO);
+		}
+
+		//ri.Free(ibo);
+	}
+
+	tr.numVBOs = 0;
+	tr.numIBOs = 0;
+}
+
+/*
+============
+R_VBOList_f
+============
+*/
+void R_VBOList_f(void)
+{
+	int             i;
+	VBO_t          *vbo;
+	IBO_t          *ibo;
+	int             vertexesSize = 0;
+	int             indexesSize = 0;
+
+	ri.Printf(PRINT_ALL, " size          name\n");
+	ri.Printf(PRINT_ALL, "----------------------------------------------------------\n");
+
+	for(i = 0; i < tr.numVBOs; i++)
+	{
+		vbo = tr.vbos[i];
+
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vbo->vertexesSize / (1024 * 1024),
+				  (vbo->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vbo->name);
+
+		vertexesSize += vbo->vertexesSize;
+	}
+
+	for(i = 0; i < tr.numIBOs; i++)
+	{
+		ibo = tr.ibos[i];
+
+		ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", ibo->indexesSize / (1024 * 1024),
+				  (ibo->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), ibo->name);
+
+		indexesSize += ibo->indexesSize;
+	}
+
+	ri.Printf(PRINT_ALL, " %i total VBOs\n", tr.numVBOs);
+	ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024),
+			  (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
+
+	ri.Printf(PRINT_ALL, " %i total IBOs\n", tr.numIBOs);
+	ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024),
+			  (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
+}
+
+
+/*
+==============
+RB_UpdateVBOs
+
+Adapted from Tess_UpdateVBOs from xreal
+
+Tr3B: update the default VBO to replace the client side vertex arrays
+==============
+*/
+void RB_UpdateVBOs(unsigned int attribBits)
+{
+	GLimp_LogComment("--- RB_UpdateVBOs ---\n");
+
+	backEnd.pc.c_dynamicVboDraws++;
+
+	// update the default VBO
+	if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES)
+	{
+		R_BindVBO(tess.vbo);
+
+		if(attribBits & ATTR_BITS)
+		{
+			if(attribBits & ATTR_POSITION)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
+			}
+
+			if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD)
+			{
+				// these are interleaved, so we update both if either need it
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2);
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
+			}
+
+			if(attribBits & ATTR_NORMAL)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
+			}
+
+#ifdef USE_VERT_TANGENT_SPACE
+			if(attribBits & ATTR_TANGENT)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
+			}
+
+			if(attribBits & ATTR_BITANGENT)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_bitangent, tess.numVertexes * sizeof(tess.bitangent[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_bitangent,   tess.numVertexes * sizeof(tess.bitangent[0]),        tess.bitangent);
+			}
+#endif
+
+			if(attribBits & ATTR_COLOR)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
+			}
+
+			if(attribBits & ATTR_LIGHTDIRECTION)
+			{
+				//ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0]));
+				qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
+			}
+		}
+		else
+		{
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz,         tess.numVertexes * sizeof(tess.xyz[0]),              tess.xyz);
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st,          tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords);
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal,      tess.numVertexes * sizeof(tess.normal[0]),           tess.normal);
+#ifdef USE_VERT_TANGENT_SPACE
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent,     tess.numVertexes * sizeof(tess.tangent[0]),          tess.tangent);
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_bitangent,   tess.numVertexes * sizeof(tess.bitangent[0]),        tess.bitangent);
+#endif
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]),     tess.vertexColors);
+			qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir,    tess.numVertexes * sizeof(tess.lightdir[0]),         tess.lightdir);
+		}
+
+	}
+
+	// update the default IBO
+	if(tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
+	{
+		R_BindIBO(tess.ibo);
+
+		qglBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes);
+	}
+}

Added: trunk/code/rend2/tr_world.c
===================================================================
--- trunk/code/rend2/tr_world.c	                        (rev 0)
+++ trunk/code/rend2/tr_world.c	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,851 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+
+
+/*
+================
+R_CullSurface
+
+Tries to cull surfaces before they are lighted or
+added to the sorting list.
+================
+*/
+static qboolean	R_CullSurface( msurface_t *surf ) {
+	if ( r_nocull->integer || surf->cullinfo.type == CULLINFO_NONE) {
+		return qfalse;
+	}
+
+	if (surf->cullinfo.type & CULLINFO_PLANE)
+	{
+		// Only true for SF_FACE, so treat like its own function
+		float			d;
+		cullType_t ct;
+
+		if ( !r_facePlaneCull->integer ) {
+			return qfalse;
+		}
+
+		ct = surf->shader->cullType;
+
+		if (ct == CT_TWO_SIDED)
+		{
+			return qfalse;
+		}
+
+		// don't cull for depth shadow
+		/*
+		if ( tr.viewParms.flags & VPF_DEPTHSHADOW )
+		{
+			return qfalse;
+		}
+		*/
+
+		// shadowmaps draw back surfaces
+		if ( tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW) )
+		{
+			if (ct == CT_FRONT_SIDED)
+			{
+				ct = CT_BACK_SIDED;
+			}
+			else
+			{
+				ct = CT_FRONT_SIDED;
+			}
+		}
+
+		// do proper cull for orthographic projection
+		if (tr.viewParms.flags & VPF_ORTHOGRAPHIC) {
+			d = DotProduct(tr.viewParms.or.axis[0], surf->cullinfo.plane.normal);
+			if ( ct == CT_FRONT_SIDED ) {
+				if (d > 0)
+					return qtrue;
+			} else {
+				if (d < 0)
+					return qtrue;
+			}
+			return qfalse;
+		}
+
+		d = DotProduct (tr.or.viewOrigin, surf->cullinfo.plane.normal);
+
+		// don't cull exactly on the plane, because there are levels of rounding
+		// through the BSP, ICD, and hardware that may cause pixel gaps if an
+		// epsilon isn't allowed here 
+		if ( ct == CT_FRONT_SIDED ) {
+			if ( d < surf->cullinfo.plane.dist - 8 ) {
+				return qtrue;
+			}
+		} else {
+			if ( d > surf->cullinfo.plane.dist + 8 ) {
+				return qtrue;
+			}
+		}
+
+		return qfalse;
+	}
+
+	if (surf->cullinfo.type & CULLINFO_SPHERE)
+	{
+		int 	sphereCull;
+
+		if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) {
+			sphereCull = R_CullLocalPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius );
+		} else {
+			sphereCull = R_CullPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius );
+		}
+
+		if ( sphereCull == CULL_OUT )
+		{
+			return qtrue;
+		}
+
+		if ( sphereCull == CULL_IN )
+		{
+			return qfalse;
+		}
+	}
+
+	if (surf->cullinfo.type & CULLINFO_BOX)
+	{
+		int boxCull;
+
+		if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) {
+			boxCull = R_CullLocalBox( surf->cullinfo.bounds );
+		} else {
+			boxCull = R_CullBox( surf->cullinfo.bounds );
+		}
+
+		if ( boxCull == CULL_OUT )
+		{
+			return qtrue;
+		}
+
+		if ( boxCull == CULL_IN )
+		{
+			return qfalse;
+		}
+	}
+
+	return qfalse;
+}
+
+
+/*
+====================
+R_DlightSurface
+
+The given surface is going to be drawn, and it touches a leaf
+that is touched by one or more dlights, so try to throw out
+more dlights if possible.
+====================
+*/
+static int R_DlightSurface( msurface_t *surf, int dlightBits ) {
+	float       d;
+	int         i;
+	dlight_t    *dl;
+	
+	if ( surf->cullinfo.type & CULLINFO_PLANE )
+	{
+		int i;
+		for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+			if ( ! ( dlightBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			dl = &tr.refdef.dlights[i];
+			d = DotProduct( dl->origin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist;
+			if ( d < -dl->radius || d > dl->radius ) {
+				// dlight doesn't reach the plane
+				dlightBits &= ~( 1 << i );
+			}
+		}
+	}
+	
+	if ( surf->cullinfo.type & CULLINFO_BOX )
+	{
+		for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+			if ( ! ( dlightBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			dl = &tr.refdef.dlights[i];
+			if ( dl->origin[0] - dl->radius > surf->cullinfo.bounds[1][0]
+				|| dl->origin[0] + dl->radius < surf->cullinfo.bounds[0][0]
+				|| dl->origin[1] - dl->radius > surf->cullinfo.bounds[1][1]
+				|| dl->origin[1] + dl->radius < surf->cullinfo.bounds[0][1]
+				|| dl->origin[2] - dl->radius > surf->cullinfo.bounds[1][2]
+				|| dl->origin[2] + dl->radius < surf->cullinfo.bounds[0][2] ) {
+				// dlight doesn't reach the bounds
+				dlightBits &= ~( 1 << i );
+			}
+		}
+	}
+
+	if ( surf->cullinfo.type & CULLINFO_SPHERE )
+	{
+		for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+			if ( ! ( dlightBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			dl = &tr.refdef.dlights[i];
+			if (!SpheresIntersect(dl->origin, dl->radius, surf->cullinfo.localOrigin, surf->cullinfo.radius))
+			{
+				// dlight doesn't reach the bounds
+				dlightBits &= ~( 1 << i );
+			}
+		}
+	}
+
+	if ( *surf->data == SF_FACE ) {
+		((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = dlightBits;
+	} else if ( *surf->data == SF_GRID ) {
+		((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ]    = dlightBits;
+	} else if ( *surf->data == SF_TRIANGLES ) {
+		((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ]   = dlightBits;
+	} else if ( *surf->data == SF_VBO_MESH ) {
+		((srfVBOMesh_t *)surf->data)->dlightBits[ tr.smpFrame ]     = dlightBits;
+	} else {
+		dlightBits = 0;
+	}
+
+	if ( dlightBits ) {
+		tr.pc.c_dlightSurfaces++;
+	}
+
+	return dlightBits;
+}
+
+/*
+====================
+R_PshadowSurface
+
+Just like R_DlightSurface, cull any we can
+====================
+*/
+static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) {
+	float       d;
+	int         i;
+	pshadow_t    *ps;
+	
+	if ( surf->cullinfo.type & CULLINFO_PLANE )
+	{
+		int i;
+		for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) {
+			if ( ! ( pshadowBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			ps = &tr.refdef.pshadows[i];
+			d = DotProduct( ps->lightOrigin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist;
+			if ( d < -ps->lightRadius || d > ps->lightRadius ) {
+				// pshadow doesn't reach the plane
+				pshadowBits &= ~( 1 << i );
+			}
+		}
+	}
+	
+	if ( surf->cullinfo.type & CULLINFO_BOX )
+	{
+		for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) {
+			if ( ! ( pshadowBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			ps = &tr.refdef.pshadows[i];
+			if ( ps->lightOrigin[0] - ps->lightRadius > surf->cullinfo.bounds[1][0]
+				|| ps->lightOrigin[0] + ps->lightRadius < surf->cullinfo.bounds[0][0]
+				|| ps->lightOrigin[1] - ps->lightRadius > surf->cullinfo.bounds[1][1]
+				|| ps->lightOrigin[1] + ps->lightRadius < surf->cullinfo.bounds[0][1]
+				|| ps->lightOrigin[2] - ps->lightRadius > surf->cullinfo.bounds[1][2]
+				|| ps->lightOrigin[2] + ps->lightRadius < surf->cullinfo.bounds[0][2] 
+				|| BoxOnPlaneSide(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1], &ps->cullPlane) == 2 ) {
+				// pshadow doesn't reach the bounds
+				pshadowBits &= ~( 1 << i );
+			}
+		}
+	}
+
+	if ( surf->cullinfo.type & CULLINFO_SPHERE )
+	{
+		for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) {
+			if ( ! ( pshadowBits & ( 1 << i ) ) ) {
+				continue;
+			}
+			ps = &tr.refdef.pshadows[i];
+			if (!SpheresIntersect(ps->viewOrigin, ps->viewRadius, surf->cullinfo.localOrigin, surf->cullinfo.radius)
+				|| DotProduct( surf->cullinfo.localOrigin, ps->cullPlane.normal ) - ps->cullPlane.dist < -surf->cullinfo.radius)
+			{
+				// pshadow doesn't reach the bounds
+				pshadowBits &= ~( 1 << i );
+			}
+		}
+	}
+
+	if ( *surf->data == SF_FACE ) {
+		((srfSurfaceFace_t *)surf->data)->pshadowBits[ tr.smpFrame ] = pshadowBits;
+	} else if ( *surf->data == SF_GRID ) {
+		((srfGridMesh_t *)surf->data)->pshadowBits[ tr.smpFrame ]    = pshadowBits;
+	} else if ( *surf->data == SF_TRIANGLES ) {
+		((srfTriangles_t *)surf->data)->pshadowBits[ tr.smpFrame ]   = pshadowBits;
+	} else if ( *surf->data == SF_VBO_MESH ) {
+		((srfVBOMesh_t *)surf->data)->pshadowBits[ tr.smpFrame ]     = pshadowBits;
+	} else {
+		pshadowBits = 0;
+	}
+
+	if ( pshadowBits ) {
+		//tr.pc.c_dlightSurfaces++;
+	}
+
+	return pshadowBits;
+}
+
+
+/*
+======================
+R_AddWorldSurface
+======================
+*/
+static void R_AddWorldSurface( msurface_t *surf, int dlightBits, int pshadowBits ) {
+	// FIXME: bmodel fog?
+
+	// try to cull before dlighting or adding
+	if ( R_CullSurface( surf ) ) {
+		return;
+	}
+
+	// check for dlighting
+	if ( dlightBits ) {
+		dlightBits = R_DlightSurface( surf, dlightBits );
+		dlightBits = ( dlightBits != 0 );
+	}
+
+	// check for pshadows
+	/*if ( pshadowBits ) */{
+		pshadowBits = R_PshadowSurface( surf, pshadowBits);
+		pshadowBits = ( pshadowBits != 0 );
+	}
+
+	R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits, pshadowBits );
+}
+
+/*
+=============================================================
+
+	BRUSH MODELS
+
+=============================================================
+*/
+
+/*
+=================
+R_AddBrushModelSurfaces
+=================
+*/
+void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) {
+	bmodel_t	*bmodel;
+	int			clip;
+	model_t		*pModel;
+	int			i;
+
+	pModel = R_GetModelByHandle( ent->e.hModel );
+
+	bmodel = pModel->bmodel;
+
+	clip = R_CullLocalBox( bmodel->bounds );
+	if ( clip == CULL_OUT ) {
+		return;
+	}
+	
+	R_SetupEntityLighting( &tr.refdef, ent );
+	R_DlightBmodel( bmodel );
+
+	for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) {
+		int surf = bmodel->firstSurface + i;
+
+		if (tr.world->surfacesViewCount[surf] != tr.viewCount)
+		{
+			tr.world->surfacesViewCount[surf] = tr.viewCount;
+			R_AddWorldSurface( tr.world->surfaces + surf, tr.currentEntity->needDlights, 0 );
+		}
+	}
+}
+
+
+/*
+=============================================================
+
+	WORLD MODEL
+
+=============================================================
+*/
+
+
+/*
+================
+R_RecursiveWorldNode
+================
+*/
+static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits, int pshadowBits ) {
+
+	do {
+		int			newDlights[2];
+		unsigned int newPShadows[2];
+
+		// if the node wasn't marked as potentially visible, exit
+		// pvs is skipped for depth shadows
+		if (!(tr.viewParms.flags & VPF_DEPTHSHADOW) && node->visCounts[tr.visIndex] != tr.visCounts[tr.visIndex]) {
+			return;
+		}
+
+		// if the bounding volume is outside the frustum, nothing
+		// inside can be visible OPTIMIZE: don't do this all the way to leafs?
+
+		if ( !r_nocull->integer ) {
+			int		r;
+
+			if ( planeBits & 1 ) {
+				r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]);
+				if (r == 2) {
+					return;						// culled
+				}
+				if ( r == 1 ) {
+					planeBits &= ~1;			// all descendants will also be in front
+				}
+			}
+
+			if ( planeBits & 2 ) {
+				r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]);
+				if (r == 2) {
+					return;						// culled
+				}
+				if ( r == 1 ) {
+					planeBits &= ~2;			// all descendants will also be in front
+				}
+			}
+
+			if ( planeBits & 4 ) {
+				r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]);
+				if (r == 2) {
+					return;						// culled
+				}
+				if ( r == 1 ) {
+					planeBits &= ~4;			// all descendants will also be in front
+				}
+			}
+
+			if ( planeBits & 8 ) {
+				r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]);
+				if (r == 2) {
+					return;						// culled
+				}
+				if ( r == 1 ) {
+					planeBits &= ~8;			// all descendants will also be in front
+				}
+			}
+
+			if ( planeBits & 16 ) {
+				r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[4]);
+				if (r == 2) {
+					return;						// culled
+				}
+				if ( r == 1 ) {
+					planeBits &= ~16;			// all descendants will also be in front
+				}
+			}
+		}
+
+		if ( node->contents != -1 ) {
+			break;
+		}
+
+		// node is just a decision point, so go down both sides
+		// since we don't care about sort orders, just go positive to negative
+
+		// determine which dlights are needed
+		newDlights[0] = 0;
+		newDlights[1] = 0;
+		if ( dlightBits ) {
+			int	i;
+
+			for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+				dlight_t	*dl;
+				float		dist;
+
+				if ( dlightBits & ( 1 << i ) ) {
+					dl = &tr.refdef.dlights[i];
+					dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist;
+					
+					if ( dist > -dl->radius ) {
+						newDlights[0] |= ( 1 << i );
+					}
+					if ( dist < dl->radius ) {
+						newDlights[1] |= ( 1 << i );
+					}
+				}
+			}
+		}
+
+		newPShadows[0] = 0;
+		newPShadows[1] = 0;
+		if ( pshadowBits ) {
+			int	i;
+
+			for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) {
+				pshadow_t	*shadow;
+				float		dist;
+
+				if ( pshadowBits & ( 1 << i ) ) {
+					shadow = &tr.refdef.pshadows[i];
+					dist = DotProduct( shadow->lightOrigin, node->plane->normal ) - node->plane->dist;
+					
+					if ( dist > -shadow->lightRadius ) {
+						newPShadows[0] |= ( 1 << i );
+					}
+					if ( dist < shadow->lightRadius ) {
+						newPShadows[1] |= ( 1 << i );
+					}
+				}
+			}
+		}
+
+		// recurse down the children, front side first
+		R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0], newPShadows[0] );
+
+		// tail recurse
+		node = node->children[1];
+		dlightBits = newDlights[1];
+		pshadowBits = newPShadows[1];
+	} while ( 1 );
+
+	{
+		// leaf node, so add mark surfaces
+		int			c;
+		int surf, *view;
+
+		tr.pc.c_leafs++;
+
+		// add to z buffer bounds
+		if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) {
+			tr.viewParms.visBounds[0][0] = node->mins[0];
+		}
+		if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) {
+			tr.viewParms.visBounds[0][1] = node->mins[1];
+		}
+		if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) {
+			tr.viewParms.visBounds[0][2] = node->mins[2];
+		}
+
+		if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) {
+			tr.viewParms.visBounds[1][0] = node->maxs[0];
+		}
+		if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) {
+			tr.viewParms.visBounds[1][1] = node->maxs[1];
+		}
+		if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) {
+			tr.viewParms.visBounds[1][2] = node->maxs[2];
+		}
+
+		// add merged and unmerged surfaces
+		if (tr.world->viewSurfaces)
+			view = tr.world->viewSurfaces + node->firstmarksurface;
+		else
+			view = tr.world->marksurfaces + node->firstmarksurface;
+
+		c = node->nummarksurfaces;
+		while (c--) {
+			// just mark it as visible, so we don't jump out of the cache derefencing the surface
+			surf = *view;
+			if (surf < 0)
+			{
+				if (tr.world->mergedSurfacesViewCount[-surf - 1] != tr.viewCount)
+				{
+					tr.world->mergedSurfacesViewCount[-surf - 1]  = tr.viewCount;
+					tr.world->mergedSurfacesDlightBits[-surf - 1] = dlightBits;
+					tr.world->mergedSurfacesPshadowBits[-surf - 1] = pshadowBits;
+				}
+				else
+				{
+					tr.world->mergedSurfacesDlightBits[-surf - 1] |= dlightBits;
+					tr.world->mergedSurfacesPshadowBits[-surf - 1] |= pshadowBits;
+				}
+			}
+			else
+			{
+				if (tr.world->surfacesViewCount[surf] != tr.viewCount)
+				{
+					tr.world->surfacesViewCount[surf] = tr.viewCount;
+					tr.world->surfacesDlightBits[surf] = dlightBits;
+					tr.world->surfacesPshadowBits[surf] = pshadowBits;
+				}
+				else
+				{
+					tr.world->surfacesDlightBits[surf] |= dlightBits;
+					tr.world->surfacesPshadowBits[surf] |= pshadowBits;
+				}
+			}
+			view++;
+		}
+	}
+
+}
+
+
+/*
+===============
+R_PointInLeaf
+===============
+*/
+static mnode_t *R_PointInLeaf( const vec3_t p ) {
+	mnode_t		*node;
+	float		d;
+	cplane_t	*plane;
+	
+	if ( !tr.world ) {
+		ri.Error (ERR_DROP, "R_PointInLeaf: bad model");
+	}
+
+	node = tr.world->nodes;
+	while( 1 ) {
+		if (node->contents != -1) {
+			break;
+		}
+		plane = node->plane;
+		d = DotProduct (p,plane->normal) - plane->dist;
+		if (d > 0) {
+			node = node->children[0];
+		} else {
+			node = node->children[1];
+		}
+	}
+	
+	return node;
+}
+
+/*
+==============
+R_ClusterPVS
+==============
+*/
+static const byte *R_ClusterPVS (int cluster) {
+	if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) {
+		return tr.world->novis;
+	}
+
+	return tr.world->vis + cluster * tr.world->clusterBytes;
+}
+
+/*
+=================
+R_inPVS
+=================
+*/
+qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ) {
+	mnode_t *leaf;
+	byte	*vis;
+
+	leaf = R_PointInLeaf( p1 );
+	vis = ri.CM_ClusterPVS( leaf->cluster ); // why not R_ClusterPVS ??
+	leaf = R_PointInLeaf( p2 );
+
+	if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) {
+		return qfalse;
+	}
+	return qtrue;
+}
+
+/*
+===============
+R_MarkLeaves
+
+Mark the leaves and nodes that are in the PVS for the current
+cluster
+===============
+*/
+static void R_MarkLeaves (void) {
+	const byte	*vis;
+	mnode_t	*leaf, *parent;
+	int		i;
+	int		cluster;
+
+	// lockpvs lets designers walk around to determine the
+	// extent of the current pvs
+	if ( r_lockpvs->integer ) {
+		return;
+	}
+
+	// current viewcluster
+	leaf = R_PointInLeaf( tr.viewParms.pvsOrigin );
+	cluster = leaf->cluster;
+
+	// if the cluster is the same and the area visibility matrix
+	// hasn't changed, we don't need to mark everything again
+
+	for(i = 0; i < MAX_VISCOUNTS; i++)
+	{
+		if(tr.visClusters[i] == cluster)
+		{
+			//tr.visIndex = i;
+			break;
+		}
+	}
+
+	// if r_showcluster was just turned on, remark everything 
+	if(i != MAX_VISCOUNTS && !tr.refdef.areamaskModified && !r_showcluster->modified)// && !r_dynamicBspOcclusionCulling->modified)
+	{
+		if(tr.visClusters[i] != tr.visClusters[tr.visIndex] && r_showcluster->integer)
+		{
+			ri.Printf(PRINT_ALL, "found cluster:%i  area:%i  index:%i\n", cluster, leaf->area, i);
+		}
+		tr.visIndex = i;
+		return;
+	}
+
+	// if the areamask was modified, invalidate all visclusters
+	// this caused doors to open into undrawn areas
+	if (tr.refdef.areamaskModified)
+	{
+		memset(tr.visClusters, -2, sizeof(tr.visClusters));
+	}
+
+	tr.visIndex = (tr.visIndex + 1) % MAX_VISCOUNTS;
+	tr.visCounts[tr.visIndex]++;
+	tr.visClusters[tr.visIndex] = cluster;
+
+	if ( r_showcluster->modified || r_showcluster->integer ) {
+		r_showcluster->modified = qfalse;
+		if ( r_showcluster->integer ) {
+			ri.Printf( PRINT_ALL, "cluster:%i  area:%i\n", cluster, leaf->area );
+		}
+	}
+
+	// set all nodes to visible if there is no vis
+	// this caused some levels to simply not render
+	if (r_novis->integer || !tr.world->vis || tr.visClusters[tr.visIndex] == -1) {
+		for (i=0 ; i<tr.world->numnodes ; i++) {
+			if (tr.world->nodes[i].contents != CONTENTS_SOLID) {
+				tr.world->nodes[i].visCounts[tr.visIndex] = tr.visCounts[tr.visIndex];
+			}
+		}
+		return;
+	}
+
+	vis = R_ClusterPVS(tr.visClusters[tr.visIndex]);
+	
+	for (i=0,leaf=tr.world->nodes ; i<tr.world->numnodes ; i++, leaf++) {
+		cluster = leaf->cluster;
+		if ( cluster < 0 || cluster >= tr.world->numClusters ) {
+			continue;
+		}
+
+		// check general pvs
+		if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) {
+			continue;
+		}
+
+		// check for door connection
+		if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) {
+			continue;		// not visible
+		}
+
+		parent = leaf;
+		do {
+			if(parent->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex])
+				break;
+			parent->visCounts[tr.visIndex] = tr.visCounts[tr.visIndex];
+			parent = parent->parent;
+		} while (parent);
+	}
+}
+
+
+/*
+=============
+R_AddWorldSurfaces
+=============
+*/
+void R_AddWorldSurfaces (void) {
+	if ( !r_drawworld->integer ) {
+		return;
+	}
+
+	if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+		return;
+	}
+
+	tr.currentEntityNum = REFENTITYNUM_WORLD;
+	tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT;
+
+	// determine which leaves are in the PVS / areamask
+	if (!(tr.viewParms.flags & VPF_DEPTHSHADOW))
+		R_MarkLeaves ();
+
+	// clear out the visible min/max
+	ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] );
+
+	// perform frustum culling and flag all the potentially visible surfaces
+	if ( tr.refdef.num_dlights > 32 ) {
+		tr.refdef.num_dlights = 32 ;
+	}
+
+	if ( tr.refdef.num_pshadows > 32 ) {
+		tr.refdef.num_pshadows = 32 ;
+	}
+
+	if ( tr.viewParms.flags & VPF_DEPTHSHADOW )
+	{
+		R_RecursiveWorldNode( tr.world->nodes, 31, 0, 0);
+	}
+	else if ( !(tr.viewParms.flags & VPF_SHADOWMAP) )
+	{
+		R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1, ( 1 << tr.refdef.num_pshadows ) - 1 );
+	}
+	else
+	{
+		R_RecursiveWorldNode( tr.world->nodes, 31, ( 1 << tr.refdef.num_dlights ) - 1, 0 );
+	}
+
+	// now add all the potentially visible surfaces
+	// also mask invisible dlights for next frame
+	{
+		int i;
+
+		tr.refdef.dlightMask = 0;
+
+		for (i = 0; i < tr.world->numWorldSurfaces; i++)
+		{
+			if (tr.world->surfacesViewCount[i] != tr.viewCount)
+				continue;
+
+			R_AddWorldSurface( tr.world->surfaces + i, tr.world->surfacesDlightBits[i], tr.world->surfacesPshadowBits[i] );
+			tr.refdef.dlightMask |= tr.world->surfacesDlightBits[i];
+		}
+		for (i = 0; i < tr.world->numMergedSurfaces; i++)
+		{
+			if (tr.world->mergedSurfacesViewCount[i] != tr.viewCount)
+				continue;
+
+			R_AddWorldSurface( tr.world->mergedSurfaces + i, tr.world->mergedSurfacesDlightBits[i], tr.world->mergedSurfacesPshadowBits[i] );
+			tr.refdef.dlightMask |= tr.world->mergedSurfacesDlightBits[i];
+		}
+
+		tr.refdef.dlightMask = ~tr.refdef.dlightMask;
+	}
+}

Added: trunk/rend2-readme.txt
===================================================================
--- trunk/rend2-readme.txt	                        (rev 0)
+++ trunk/rend2-readme.txt	2012-10-26 01:23:06 UTC (rev 2329)
@@ -0,0 +1,601 @@
+Rend2
+<insert ascii art here>
+
+Rend2 is an alternate renderer for ioquake3.  It aims to implement modern
+features and technologies into the id tech 3 engine, but without sacrificing
+compatibility with existing Quake 3 mods.
+
+
+-------------------------------------------------------------------------------
+  FEATURES
+-------------------------------------------------------------------------------
+
+  - Compatible with most vanilla Quake 3 mods.
+  - Faster than original renderer on modern GPUs.
+  - HDR Rendering, and support for HDR lightmaps
+  - Tone mapping and auto-exposure.
+  - Cascaded shadow maps.
+  - Multisample anti-aliasing.
+  - Texture upsampling.
+  - Advanced materials support.
+  - Advanced shading and specular methods.
+  - sRGB support.
+  - LATC and BPTC texture compression support.
+  - Screen-space ambient occlusion.
+
+
+-------------------------------------------------------------------------------
+  COMPILATION
+-------------------------------------------------------------------------------
+
+For *nix/MinGW:
+
+1. Download an appropriate version of the ioq3 source code.  For version 32 of
+   Rend2, r2328 should do, though the latest may work as well.  For 
+   details on how to do this, see http://ioquake3.org/get-it/source-codes/ .
+   
+2. Copy the patch file (for v32, vbos-glsl-31a.diff) into the directory you put
+   the ioq3 source code.  There should be a README in that directory.
+   
+3. Run 'patch -p0 <vbos-glsl-31a.diff' then 'make'.
+
+Compiling on different platforms and with different compilers hasn't been
+tested.  The MSVC project file should work, but it hasn't been tested.
+
+
+-------------------------------------------------------------------------------
+  INSTALLATION
+-------------------------------------------------------------------------------
+
+For *nix:
+
+1. This should be identical to installing ioq3.  Check their README for more
+   details.
+
+   
+For Win32:
+
+1. Have a Quake 3 install, fully patched.
+
+2. Copy the following files into Quake 3's install directory: 
+     
+     ioquake3.x86.exe
+     renderer_opengl1_x86.dll
+     renderer_rend2_x86.dll
+     
+   These can be found in build/release-mingw32-x86 after compiling, or bug
+   someone to release binaries.
+  
+
+-------------------------------------------------------------------------------
+  RUNNING
+-------------------------------------------------------------------------------
+
+1. Start ioquake3. (ioquake3.x86.exe on Win32)
+ 
+2. Open the console (default key ~) and type '/cl_renderer rend2; vid_restart'
+
+3. Enjoy.
+
+
+-------------------------------------------------------------------------------
+  CVARS
+-------------------------------------------------------------------------------
+
+Cvars for simple rendering features:
+  r_ext_compressed_textures       - Automatically compress textures.
+                                     0 - No texture compression. (default)
+                                     1 - DXT/LATC texture compression if
+                                         supported.
+                                     2 - BPTC texture compression if supported.
+
+  r_ext_framebuffer_multisample  - Multisample Anti-aliasing.
+                                     0    - None. (default)
+                                     1-16 - Some.
+                                     17+  - Too much!
+									 
+  r_ssao                         - Enable screen-space ambient occlusion.
+                                   Currently eats framerate and has some
+								   visible artifacts.
+								     0 - No. (default)
+									 1 - Yes.
+
+Cvars for HDR and tonemapping:
+  r_hdr                          - Do scene rendering in a framebuffer with
+                                   high dynamic range.  (Less banding, and
+                                   exposure changes look much better)
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_cameraExposure               - Cheat.  Alter brightness, in powers of two.
+                                     -2  - 4x as dark.
+                                     0   - Normal. (default)
+                                     0.5 - Sqrt(2)x as bright.
+                                     2   - 4x as bright.
+
+  r_postProcess                  - Enable post-processing.
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_toneMap                      - Enable tone mapping.  Requires 
+                                   r_hdr and r_postProcess.
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_forceToneMap                 - Cheat.  Override built-in and map tonemap
+                                   settings and use cvars r_forceToneMapAvg,
+								   r_forceToneMapMin, and r_forceToneMapMax.
+								     0 - No. (default)
+									 1 - Yes.
+
+  r_forceToneMapAvg              - Cheat.  Map average scene luminance to this
+                                   value, in powers of two.  Requires 
+								   r_forceToneMap.
+								    -2.0 - Dark.
+								    -1.0 - Kinda dark. (default).
+									 2.0 - Too bright.
+
+  r_forceToneMapMin              - Cheat.  After mapping average, luminance
+                                   below this level is mapped to black.
+								   Requires r_forceToneMap.
+								    -5    - Not noticeable.
+									-3.25 - Normal. (default)
+									 0.0  - Too dark.
+
+  r_forceToneMapMin              - Cheat.  After mapping average, luminance
+                                   above this level is mapped to white.
+								   Requires r_forceToneMap.
+								    0.0 - Too bright.
+									1.0 - Normal. (default).
+									2.0 - Washed out.
+
+  r_autoExposure                 - Do automatic exposure based on scene
+                                   brightness.  Hardcoded to -2 to 2 on maps
+								   that don't specify otherwise.  Requires
+                                   r_hdr, r_postprocess, and r_toneMap.
+                                     0 - No.
+                                     1 - Yes. (default)
+									 
+  r_forceAutoExposure            - Cheat.  Override built-in and map auto
+                                   exposure settings and use cvars
+								   r_forceAutoExposureMin and 
+								   r_forceAutoExposureMax.
+								     0 - No. (default)
+									 1 - Yes.
+
+  r_forceAutoExposureMin         - Cheat.  Set minimum exposure to this value,
+                                   in powers of two.  Requires
+								   r_forceAutoExpsure.
+								    -3.0 - Dimmer.
+									-2.0 - Normal. (default)
+									-1.0 - Brighter.
+
+  r_forceAutoExposureMax         - Cheat.  Set maximum exposure to this value,
+                                   in powers of two.  Requires
+								   r_forceAutoExpsure.
+								     1.0 - Dimmer.
+									 2.0 - Normal. (default)
+									 3.0 - Brighter.
+
+  r_srgb                         - Treat all input textures as sRGB, and do
+                                   final rendering in a sRGB framebuffer.  Only
+								   required if assets were created with it in
+								   mind.
+                                     0 - No. (default)
+                                     1 - Yes.
+
+Cvars for advanced material usage:
+  r_normalMapping                - Enable normal mapping for materials that
+                                   support it, and also specify advanced 
+                                   shading techniques.
+                                     0 - No.
+                                     1 - Yes. (default)
+                                     2 - Yes, and use Oren-Nayar reflectance
+                                         model.
+								     3 - Yes, and use tri-Ace's Oren-Nayar
+									     reflectance model.
+
+  r_specularMapping              - Enable specular mapping for materials that
+                                   support it, and also specify advanced
+                                   specular techniques.
+                                     0 - No.
+                                     1 - Yes, and use tri-Ace. (default)
+                                     2 - Yes, and use Blinn-Phong.
+                                     3 - Yes, and use Cook-Torrance.
+                                     4 - Yes, and use Torrance-Sparrow.
+
+  r_deluxeMapping                - Enable deluxe mapping.  (Map is compiled
+                                   with light directions.)  Even if the map 
+                                   doesn't have deluxe mapping compiled in,
+                                   an approximation based on the lightgrid
+                                   will be used.
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_parallaxMapping              - Enable parallax mapping for materials that
+                                   support it.
+                                     0 - No. (default)
+                                     1 - Yes.
+
+Cvars for image interpolation and generation:
+  r_imageUpsample                - Use interpolation to artifically increase
+                                   the resolution of all textures.  Looks good
+                                   in certain circumstances.
+                                     0 - No. (default)
+                                     1 - 2x size.
+                                     2 - 4x size.
+                                     3 - 8x size, etc
+
+  r_imageUpsampleMaxSize         - Maximum texture size when upsampling
+                                   textures.
+                                     1024 - Default.
+                                     2048 - Really nice.
+                                     4096 - Really slow.
+                                     8192 - Crash.
+
+  r_imageUpsampleType            - Type of interpolation when upsampling
+                                   textures.
+                                     0 - None. (probably broken)
+                                     1 - Bad but fast (default,
+                                         FCBI without second derivatives)
+                                     2 - Okay but slow (normal FCBI)
+
+  r_genNormalMaps                - Naively generate normal maps for all
+                                   textures.
+                                     0 - Don't. (default)
+                                     1 - Do.
+
+Cvars for the sunlight and cascaded shadow maps:
+  r_forceSun                     - Cheat. Force sunlight and shadows, using sun
+                                   position from sky material.
+                                     0 - Don't. (default)
+                                     1 - Do.
+                                     2 - Sunrise, sunset.
+
+  r_forceSunMapLightScale        - Cheat. Scale map brightness by this factor
+                                   when r_forceSun 1.
+                                     0.5 - Default
+                                     
+  r_forceSunLightScale           - Cheat. Scale sun brightness by this factor
+                                   when r_forceSun 1.
+                                     0.5 - Default
+                                     
+  r_forceSunAmbientScale         - Cheat. Scale sun ambient brightness by this 
+                                   factor when r_forceSun 1.
+                                     0.2 - Default
+
+  r_sunShadows                   - Enable sunlight and cascaded shadow maps for
+                                   it on maps that support it.
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_shadowFilter                 - Enable filtering shadows for a smoother
+                                   look.
+                                     0 - No.
+                                     1 - Some. (default)
+                                     2 - Much.
+
+  r_shadowMapSize                - Size of each cascaded shadow map.
+                                     256  - 256x256, ugly, probably shouldn't
+                                             go below this.
+                                     512  - 512x512, passable.
+                                     1024 - 1024x1024, good. (default)
+                                     2048 - 2048x2048, extreme.
+                                     4096 - 4096x4096, indistinguishable from
+                                            2048.
+  
+Cvars that you probably don't care about or shouldn't mess with:
+  r_mergeMultidraws              - Optimize number of calls to 
+                                   glMultiDrawElements().
+                                     0 - Don't.
+                                     1 - Do some. (default)
+                                     2 - Do more than necessary (eats CPU).
+
+  r_mergeLeafSurfaces            - Merge surfaces that share common materials
+                                   and a common leaf.  Speeds up rendering.
+                                     0 - Don't.
+                                     1 - Do. (default)
+
+  r_recalcMD3Normals             - Recalculate the normals when loading an MD3.
+                                   Fixes normal maps in some cases but looks
+                                   ugly in others.
+                                     0 - Don't. (default)
+                                     1 - Do.
+
+  r_depthPrepass                 - Do a depth-only pass before rendering.
+                                   Speeds up rendering in cases where advanced
+                                   features are used.  Required for
+                                   r_sunShadows.
+                                     0 - No.
+                                     1 - Yes. (default)
+
+  r_normalAmbient                - Split map light into ambient and directed
+                                   portions when doing deluxe mapping.  Not
+                                   very useful.
+                                     0   - Don't. (default).
+                                     0.3 - 30% ambient, 70% directed.
+                                     1.0 - 100% ambient.
+
+  r_mergeLightmaps               - Merge the small (128x128) lightmaps into 
+                                   2 or fewer giant (4096x4096) lightmaps.
+                                   Easy speedup.
+                                     0 - Don't.
+                                     1 - Do. (default)
+
+  r_shadowCascadeZNear           - Near plane for shadow cascade frustums.
+                                     4 - Default.
+
+  r_shadowCascadeZFar            - Far plane for shadow cascade frustums.
+                                     3072 - Default.
+
+  r_shadowCascadeZBias           - Z-bias for shadow cascade frustums.
+                                     -256 - Default.
+
+  
+Cvars that have broken bits:
+  r_dlightMode                   - Change how dynamic lights look.
+                                     0 - Quake 3 style dlights, fake
+                                         brightening. (default)
+                                     1 - Actual lighting, no shadows.
+                                     2 - Light and shadows. (broken)
+
+  r_pshadowDist                  - Virtual camera distance when creating shadow
+                                   maps for projected shadows.  Deprecated.
+                                   
+  cg_shadows                     - Old shadow code.  Deprecated.
+
+
+-------------------------------------------------------------------------------
+  MATERIALS
+-------------------------------------------------------------------------------
+
+Rend2 supports .mtr files, which are basically the same as .shader files, and
+are located in the same place, but override existing .shader files if they 
+exist.  This is to allow maps and mods to use the new material features without
+breaking the map when using the old renderer.
+
+Here's an example of a material stored in one, showing off some new features:
+
+    textures/abandon/grass
+    {
+        qer_editorimage textures/abandon/grass.jpg
+        {
+            map textures/abandon/grass3_256_d.jpg
+            rgbgen identity
+        }
+        {
+            stage normalparallaxmap
+            map textures/abandon/grass3_1024_n.png
+        }
+        {
+            stage specularmap
+            map textures/abandon/grass3_256_s.png
+            specularReflectance 0.12
+            specularExponent 16
+        }
+        {
+            map $lightmap
+            blendfunc GL_DST_COLOR GL_ZERO
+        }
+    }
+
+The first thing to notice is that this is basically the same as old Quake 3 
+shader files.  The next thing to notice are the new keywords.  Here is what 
+they mean:
+
+  stage <type>        
+    - State how this imagemap will be used by Rend2:
+        diffuseMap        - Standard, same as no stage entry
+        normalMap         - Image will be used as a normal map
+        normalParallaxMap - Image will be used as a normal map with 
+                            alpha treated as height for parallax mapping
+        specularMap       - Image will be used as a specular map with
+                            alpha treated as shininess.
+
+  specularReflectance <value> 
+    - State how metallic this material is.  Metals typically have a high 
+      specular and a low diffuse, so this is typically high for them, and low
+      for other materials, such as plastic.  For typical values for various
+      materials, see http://refractiveindex.info , pick a material, then scroll
+      down to the reflection calculator and look up its reflectance.  Default
+      is 0.04, since most materials aren't metallic.
+  
+  specularExponent <value>
+    - State how shiny this material is.  Note that this is modulated by the 
+      alpha channel of the specular map, so if it were set to 16, and the alpha
+      channel of the specular map was set to 0.5, then the shininess would be
+      set to 8.  Default 256.
+      
+An important note is that normal and specular maps influence the diffuse map
+declared before them, so materials like this are possible:
+
+    textures/terrain/grass
+    {
+        qer_editorimage textures/terrain/grass.jpg
+
+        {
+            map textures/terrain/rock.jpg
+        }
+        {
+            stage normalparallaxmap
+            map textures/terrain/rock_n.png
+        }
+        {
+            stage specularmap
+            map textures/terrain/rock_s.jpg
+        }
+        {
+            map textures/terrain/grass.jpg
+            blendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
+            alphaGen vertex
+        }
+        {
+            stage normalparallaxmap
+            map textures/terrain/grass_n.png
+        }
+        {
+            stage specularmap
+            map textures/terrain/grass_s.png
+            specularReflectance 0.12
+        }
+        {
+            map $lightmap
+            blendfunc GL_DST_COLOR GL_ZERO
+        }
+    }
+    
+Though note due to the complexity of lighting, dynamic light (including
+sunlight with cascaded shadow maps) currently only works 100% on materials like
+this, where the second diffuse map doesn't have its own alpha, and only
+uses vertex alpha.  YMMV.
+
+Another addition to materials is working normal/specular maps on vertex lit
+surfaces.  To enable this, make your material look like this:
+
+    textures/vehicles/car
+    {
+        qer_editorimage textures/vehicles/car.jpg
+
+        {
+            map textures/vehicles/car.jpg
+            rgbGen vertexLit
+        }
+        {
+            stage normalparallaxmap
+            map textures/vehicles/car_n.jpg
+        }
+        {
+            stage specularmap
+            map textures/vehicles/car_s.jpg
+        }
+    }
+
+Note the new keyword, 'vertexLit' after rgbGen.  This is analogous to 
+'rgbGen vertex', except a light direction will be determined from the lightgrid
+and used with the normal and specular maps.  'exactVertexLit' exists as well,
+and is the equivalent for 'exactVertex'.
+
+
+-------------------------------------------------------------------------------
+  DYNAMIC SUNLIGHT AND CASCADED SHADOW MAPS
+-------------------------------------------------------------------------------
+
+This adds a new keyword to sky materials, q3gl2_sun.  The syntax is:
+
+  q3gl2_sun <red> <green> <blue> <intensity> <degrees> <mapLightScale> 
+  <ambientLightScale>
+  
+Note the first six parameters are the same as in q3map_sun or q3map_sunExt,
+and the last two indicate scaling factors for the map brightness and an ambient
+light of the same color as the sun.
+
+There are currently two ways to use this in your own (and other people's) maps.
+
+  1. Create your map as normal and add a 'q3gl2_sun' line after your 
+     'q3map_sun' line in your sky material, like so:
+     
+    textures/skies/bluesky
+    {
+        qer_editorimage textures/skies/bluesky.jpg
+
+        surfaceparm nomarks
+        surfaceparm noimpact
+        surfaceparm nolightmap
+        surfaceparm sky
+        q3map_sunExt 240 238 200 100 195 35 3 16
+        q3gl2_sun 240 238 200 50 195 35 3 0.5 0.2
+        q3map_skylight 50 16
+        q3map_lightimage $whiteimage
+
+        skyparms env/bluesky - -
+    }
+
+     The advantages with this method are that your map will continue to work
+     with the old renderer with the sunlight baked into the lightmap, and it
+     can be used with existing maps without recompilation.  The downside is
+     artifacts like doubled shadows and uneven shadow edges.
+     
+  2. Use 'q3gl2_sun' instead of 'q3map_sun' or 'q3map_sunExt', like so:
+  
+    textures/skies/bluesky
+    {
+        qer_editorimage textures/skies/bluesky.jpg
+
+        surfaceparm nomarks
+        surfaceparm noimpact
+        surfaceparm nolightmap
+        surfaceparm sky
+        q3gl2_sun 240 238 200 50 195 35 3 0.5 0.2
+        q3map_skylight 50 16
+        q3map_lightimage $whiteimage
+
+        skyparms env/bluesky - -
+    }
+
+     The advantages with this method are that you don't get the artifacts that
+     characterize the other method, and your map compiles a lot faster without
+     the sunlight bouncing calculations.  The downsides are that your map will
+     not display properly with the old renderer, and you lose the bounced light
+     that compiling the map with q3map_sun* in it would have.
+
+     
+-------------------------------------------------------------------------------
+  TONE MAPPING AND AUTO EXPOSURE
+-------------------------------------------------------------------------------
+
+This adds a new keyword to sky materials, q3gl2_tonemap.  The syntax is:
+
+  q3gl2_tonemap <toneMapMin> <toneMapAvg> <toneMapMax <autoExposureMin>
+  <autoExposureMax>
+  
+Each of these settings corresponds to a matching cvar, so you can view and
+adjust the effect before settling on fixed settings.
+
+
+-------------------------------------------------------------------------------
+  THANKS
+-------------------------------------------------------------------------------
+
+I'd like to take this part of the readme to thank the numerous people who
+contributed thoughts, ideas, and whole swaths of code to this project.
+
+  - Id Software, for creating Quake 3 and releasing its source code under a
+    GPL license, without which this project would not be possible.
+
+  - Zachary 'Zakk' Slater, Thilo Schulz, Tim Angus, and the rest of the
+    ioquake3 team and contributors, for improving massively upon the raw Quake
+	3 source, and accepting my and gimhael's modular renderer patch.
+
+  - Robert 'Tr3B' Beckebans and the other contributors to XReaL, for letting me
+    liberally copy code from you. :)
+  
+  - Andrew 'Black Monk' Prosnik, Andrei 'Makro' Drexler, Tomi 'T.T.I.' Isoaho,
+    Richard 'JBravo' Allen, Walter 'Johnny Rocket' Somol, and the rest of the
+    Boomstick Studios, for contributing code, feature requests, and testing.
+    
+  - Yoshiharu Gotanda, Tatsuya Shoji, and the rest of tri-Ace's R&D Department,
+    for creating the tri-Ace shading equations and posting their derivations in
+	simple English.
+    
+  - Matthias 'gimhael' Bentrup, for random ideas and bits of code.
+  
+  - Evan 'megatog615' Goers, for testing, ideas, and bugging me just enough
+    that I'd write documentation. :)
+
+  - The folks at #ioquake3, who don't seem to mind when I suddenly drop a
+    screenshot and insist on talking about it. :)
+
+  - And lots of various other random people, who posted on forums, blogs, and
+    Wikipedia, who helped in small but numerous ways.
+
+If I missed you in this section, feel free to drop me a line and I'll add you.
+
+
+-------------------------------------------------------------------------------
+  CONTACT
+-------------------------------------------------------------------------------
+
+My name is James Canete, and I wrote most of this readme.  Also, a renderer.
+
+If you wish to get in touch with me, try my GMail at use.less01 (you should be
+able to solve this), or look for SmileTheory in #ioquake3 on irc.freenode.net.



More information about the quake3-commits mailing list