]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - gl_rmain.c
Lets add the ability to have silent messages sent to the chatbox, this is so that...
[xonotic/darkplaces.git] / gl_rmain.c
index 06922c1da0872e3a2699515e5cf14ed6fa9884d9..96b52e5267610abb42983ac67cb2d598e43e31ce 100644 (file)
@@ -44,6 +44,7 @@ static qboolean r_loadgloss;
 qboolean r_loadfog;
 static qboolean r_loaddds;
 static qboolean r_savedds;
+static qboolean r_gpuskeletal;
 
 //
 // screen size info
@@ -602,13 +603,17 @@ static void R_BuildFogHeightTexture(void)
 
 //=======================================================================================================================================================
 
-static const char *builtinshaderstring =
+static const char *builtinshaderstrings[] =
+{
 #include "shader_glsl.h"
-;
+0
+};
 
-const char *builtinhlslshaderstring =
+const char *builtinhlslshaderstrings[] =
+{
 #include "shader_hlsl.h"
-;
+0
+};
 
 char *glslshaderstring = NULL;
 char *hlslshaderstring = NULL;
@@ -624,9 +629,7 @@ shaderpermutationinfo_t;
 
 typedef struct shadermodeinfo_s
 {
-       const char *vertexfilename;
-       const char *geometryfilename;
-       const char *fragmentfilename;
+       const char *filename;
        const char *pretext;
        const char *name;
 }
@@ -671,46 +674,44 @@ shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] =
 // NOTE: MUST MATCH ORDER OF SHADERMODE_* ENUMS!
 shadermodeinfo_t glslshadermodeinfo[SHADERMODE_COUNT] =
 {
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_GENERIC\n", " generic"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_POSTPROCESS\n", " postprocess"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_FLATCOLOR\n", " flatcolor"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTMAP\n", " lightmap"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_FAKELIGHT\n", " fakelight"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_LIGHTSOURCE\n", " lightsource"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_REFRACTION\n", " refraction"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_WATER\n", " water"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_SHOWDEPTH\n", " showdepth"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"},
-       {"glsl/default.glsl", NULL, "glsl/default.glsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"},
+       {"glsl/default.glsl", "#define MODE_GENERIC\n", " generic"},
+       {"glsl/default.glsl", "#define MODE_POSTPROCESS\n", " postprocess"},
+       {"glsl/default.glsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"},
+       {"glsl/default.glsl", "#define MODE_FLATCOLOR\n", " flatcolor"},
+       {"glsl/default.glsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"},
+       {"glsl/default.glsl", "#define MODE_LIGHTMAP\n", " lightmap"},
+       {"glsl/default.glsl", "#define MODE_FAKELIGHT\n", " fakelight"},
+       {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"},
+       {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"},
+       {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"},
+       {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"},
+       {"glsl/default.glsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"},
+       {"glsl/default.glsl", "#define MODE_LIGHTSOURCE\n", " lightsource"},
+       {"glsl/default.glsl", "#define MODE_REFRACTION\n", " refraction"},
+       {"glsl/default.glsl", "#define MODE_WATER\n", " water"},
+       {"glsl/default.glsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"},
+       {"glsl/default.glsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"},
 };
 
 shadermodeinfo_t hlslshadermodeinfo[SHADERMODE_COUNT] =
 {
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_GENERIC\n", " generic"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_POSTPROCESS\n", " postprocess"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_FLATCOLOR\n", " flatcolor"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTMAP\n", " lightmap"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_FAKELIGHT\n", " fakelight"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_LIGHTSOURCE\n", " lightsource"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_REFRACTION\n", " refraction"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_WATER\n", " water"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_SHOWDEPTH\n", " showdepth"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"},
-       {"hlsl/default.hlsl", NULL, "hlsl/default.hlsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"},
+       {"hlsl/default.hlsl", "#define MODE_GENERIC\n", " generic"},
+       {"hlsl/default.hlsl", "#define MODE_POSTPROCESS\n", " postprocess"},
+       {"hlsl/default.hlsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"},
+       {"hlsl/default.hlsl", "#define MODE_FLATCOLOR\n", " flatcolor"},
+       {"hlsl/default.hlsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTMAP\n", " lightmap"},
+       {"hlsl/default.hlsl", "#define MODE_FAKELIGHT\n", " fakelight"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"},
+       {"hlsl/default.hlsl", "#define MODE_LIGHTSOURCE\n", " lightsource"},
+       {"hlsl/default.hlsl", "#define MODE_REFRACTION\n", " refraction"},
+       {"hlsl/default.hlsl", "#define MODE_WATER\n", " water"},
+       {"hlsl/default.hlsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"},
+       {"hlsl/default.hlsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"},
 };
 
 struct r_glsl_permutation_s;
@@ -975,25 +976,68 @@ static r_glsl_permutation_t *R_GLSL_FindPermutation(unsigned int mode, unsigned
        return p;
 }
 
-static char *R_GLSL_GetText(const char *filename, qboolean printfromdisknotice)
+static char *R_ShaderStrCat(const char **strings)
+{
+       char *string, *s;
+       const char **p = strings;
+       const char *t;
+       size_t len = 0;
+       for (p = strings;(t = *p);p++)
+               len += strlen(t);
+       len++;
+       s = string = (char *)Mem_Alloc(r_main_mempool, len);
+       len = 0;
+       for (p = strings;(t = *p);p++)
+       {
+               len = strlen(t);
+               memcpy(s, t, len);
+               s += len;
+       }
+       *s = 0;
+       return string;
+}
+
+static char *R_GetShaderText(const char *filename, qboolean printfromdisknotice, qboolean builtinonly)
 {
        char *shaderstring;
        if (!filename || !filename[0])
                return NULL;
+       // LordHavoc: note that FS_LoadFile appends a 0 byte to make it a valid string, so does R_ShaderStrCat
        if (!strcmp(filename, "glsl/default.glsl"))
        {
+               if (builtinonly)
+                       return R_ShaderStrCat(builtinshaderstrings);
                if (!glslshaderstring)
                {
                        glslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL);
                        if (glslshaderstring)
                                Con_DPrintf("Loading shaders from file %s...\n", filename);
                        else
-                               glslshaderstring = (char *)builtinshaderstring;
+                               glslshaderstring = R_ShaderStrCat(builtinshaderstrings);
                }
                shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(glslshaderstring) + 1);
                memcpy(shaderstring, glslshaderstring, strlen(glslshaderstring) + 1);
                return shaderstring;
        }
+       if (!strcmp(filename, "hlsl/default.hlsl"))
+       {
+               if (builtinonly)
+                       return R_ShaderStrCat(builtinhlslshaderstrings);
+               if (!hlslshaderstring)
+               {
+                       hlslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL);
+                       if (hlslshaderstring)
+                               Con_DPrintf("Loading shaders from file %s...\n", filename);
+                       else
+                               hlslshaderstring = R_ShaderStrCat(builtinhlslshaderstrings);
+               }
+               shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(hlslshaderstring) + 1);
+               memcpy(shaderstring, hlslshaderstring, strlen(hlslshaderstring) + 1);
+               return shaderstring;
+       }
+       // we don't have builtin strings for any other files
+       if (builtinonly)
+               return NULL;
        shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL);
        if (shaderstring)
        {
@@ -1009,7 +1053,7 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
        int i;
        int sampler;
        shadermodeinfo_t *modeinfo = glslshadermodeinfo + mode;
-       char *vertexstring, *geometrystring, *fragmentstring;
+       char *sourcestring;
        char permutationname[256];
        int vertstrings_count = 0;
        int geomstrings_count = 0;
@@ -1024,11 +1068,9 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
        p->program = 0;
 
        permutationname[0] = 0;
-       vertexstring   = R_GLSL_GetText(modeinfo->vertexfilename, true);
-       geometrystring = R_GLSL_GetText(modeinfo->geometryfilename, false);
-       fragmentstring = R_GLSL_GetText(modeinfo->fragmentfilename, false);
+       sourcestring  = R_GetShaderText(modeinfo->filename, true, false);
 
-       strlcat(permutationname, modeinfo->vertexfilename, sizeof(permutationname));
+       strlcat(permutationname, modeinfo->filename, sizeof(permutationname));
 
        // if we can do #version 130, we should (this improves quality of offset/reliefmapping thanks to textureGrad)
        if(vid.support.gl20shaders130)
@@ -1082,17 +1124,9 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
        fragstrings_count += shaderstaticparms_count;
 
        // now append the shader text itself
-       vertstrings_list[vertstrings_count++] = vertexstring;
-       geomstrings_list[geomstrings_count++] = geometrystring;
-       fragstrings_list[fragstrings_count++] = fragmentstring;
-
-       // if any sources were NULL, clear the respective list
-       if (!vertexstring)
-               vertstrings_count = 0;
-       if (!geometrystring)
-               geomstrings_count = 0;
-       if (!fragmentstring)
-               fragstrings_count = 0;
+       vertstrings_list[vertstrings_count++] = sourcestring;
+       geomstrings_list[geomstrings_count++] = sourcestring;
+       fragstrings_list[fragstrings_count++] = sourcestring;
 
        // compile the shader program
        if (vertstrings_count + geomstrings_count + fragstrings_count)
@@ -1258,12 +1292,8 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                Con_Printf("^1GLSL shader %s failed!  some features may not work properly.\n", permutationname);
 
        // free the strings
-       if (vertexstring)
-               Mem_Free(vertexstring);
-       if (geometrystring)
-               Mem_Free(geometrystring);
-       if (fragmentstring)
-               Mem_Free(fragmentstring);
+       if (sourcestring)
+               Mem_Free(sourcestring);
 }
 
 static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int permutation)
@@ -1295,7 +1325,7 @@ static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int per
                                }
                                if (i >= SHADERPERMUTATION_COUNT)
                                {
-                                       //Con_Printf("Could not find a working OpenGL 2.0 shader for permutation %s %s\n", shadermodeinfo[mode].vertexfilename, shadermodeinfo[mode].pretext);
+                                       //Con_Printf("Could not find a working OpenGL 2.0 shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext);
                                        r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation);
                                        qglUseProgram(0);CHECKGLERROR
                                        return; // no bit left to clear, entire mode is broken
@@ -1436,35 +1466,6 @@ static r_hlsl_permutation_t *R_HLSL_FindPermutation(unsigned int mode, unsigned
        return p;
 }
 
-static char *R_HLSL_GetText(const char *filename, qboolean printfromdisknotice)
-{
-       char *shaderstring;
-       if (!filename || !filename[0])
-               return NULL;
-       if (!strcmp(filename, "hlsl/default.hlsl"))
-       {
-               if (!hlslshaderstring)
-               {
-                       hlslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL);
-                       if (hlslshaderstring)
-                               Con_DPrintf("Loading shaders from file %s...\n", filename);
-                       else
-                               hlslshaderstring = (char *)builtinhlslshaderstring;
-               }
-               shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(hlslshaderstring) + 1);
-               memcpy(shaderstring, hlslshaderstring, strlen(hlslshaderstring) + 1);
-               return shaderstring;
-       }
-       shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL);
-       if (shaderstring)
-       {
-               if (printfromdisknotice)
-                       Con_DPrintf("from disk %s... ", filename);
-               return shaderstring;
-       }
-       return shaderstring;
-}
-
 #include <d3dx9.h>
 //#include <d3dx9shader.h>
 //#include <d3dx9mesh.h>
@@ -1533,6 +1534,18 @@ static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, c
                        {"D3DXCompileShader",                   (void **) &qD3DXCompileShader},
                        {NULL, NULL}
                };
+               // LordHavoc: the June 2010 SDK lacks these macros to make ID3DXBuffer usable in C, and to make it work in both C and C++ the macros are needed...
+#ifndef ID3DXBuffer_GetBufferPointer
+#if !defined(__cplusplus) || defined(CINTERFACE)
+#define ID3DXBuffer_GetBufferPointer(p)   (p)->lpVtbl->GetBufferPointer(p)
+#define ID3DXBuffer_GetBufferSize(p)      (p)->lpVtbl->GetBufferSize(p)
+#define ID3DXBuffer_Release(p)            (p)->lpVtbl->Release(p)
+#else
+#define ID3DXBuffer_GetBufferPointer(p)   (p)->GetBufferPointer()
+#define ID3DXBuffer_GetBufferSize(p)      (p)->GetBufferSize()
+#define ID3DXBuffer_Release(p)            (p)->Release()
+#endif
+#endif
                if (Sys_LoadLibrary(dllnames_d3dx9, &d3dx9_dll, d3dx9_dllfuncs))
                {
                        DWORD shaderflags = 0;
@@ -1613,7 +1626,7 @@ static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode
        int geomstring_length = 0;
        int fragstring_length = 0;
        char *t;
-       char *vertexstring, *geometrystring, *fragmentstring;
+       char *sourcestring;
        char *vertstring, *geomstring, *fragstring;
        char permutationname[256];
        char cachename[256];
@@ -1632,11 +1645,9 @@ static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode
 
        permutationname[0] = 0;
        cachename[0] = 0;
-       vertexstring   = R_HLSL_GetText(modeinfo->vertexfilename, true);
-       geometrystring = R_HLSL_GetText(modeinfo->geometryfilename, false);
-       fragmentstring = R_HLSL_GetText(modeinfo->fragmentfilename, false);
+       sourcestring = R_GetShaderText(modeinfo->filename, true, false);
 
-       strlcat(permutationname, modeinfo->vertexfilename, sizeof(permutationname));
+       strlcat(permutationname, modeinfo->filename, sizeof(permutationname));
        strlcat(cachename, "hlsl/", sizeof(cachename));
 
        // define HLSL so that the shader can tell apart the HLSL compiler and the Cg compiler
@@ -1695,17 +1706,9 @@ static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode
                        cachename[i] = '_';
 
        // now append the shader text itself
-       vertstrings_list[vertstrings_count++] = vertexstring;
-       geomstrings_list[geomstrings_count++] = geometrystring;
-       fragstrings_list[fragstrings_count++] = fragmentstring;
-
-       // if any sources were NULL, clear the respective list
-       if (!vertexstring)
-               vertstrings_count = 0;
-       if (!geometrystring)
-               geomstrings_count = 0;
-       if (!fragmentstring)
-               fragstrings_count = 0;
+       vertstrings_list[vertstrings_count++] = sourcestring;
+       geomstrings_list[geomstrings_count++] = sourcestring;
+       fragstrings_list[fragstrings_count++] = sourcestring;
 
        vertstring_length = 0;
        for (i = 0;i < vertstrings_count;i++)
@@ -1743,12 +1746,8 @@ static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode
                Mem_Free(geomstring);
        if (fragstring)
                Mem_Free(fragstring);
-       if (vertexstring)
-               Mem_Free(vertexstring);
-       if (geometrystring)
-               Mem_Free(geometrystring);
-       if (fragmentstring)
-               Mem_Free(fragmentstring);
+       if (sourcestring)
+               Mem_Free(sourcestring);
 }
 
 static inline void hlslVSSetParameter16f(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 4);}
@@ -1794,7 +1793,7 @@ void R_SetupShader_SetPermutationHLSL(unsigned int mode, unsigned int permutatio
                                }
                                if (i >= SHADERPERMUTATION_COUNT)
                                {
-                                       //Con_Printf("Could not find a working HLSL shader for permutation %s %s\n", shadermodeinfo[mode].vertexfilename, shadermodeinfo[mode].pretext);
+                                       //Con_Printf("Could not find a working HLSL shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext);
                                        r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation);
                                        return; // no bit left to clear, entire mode is broken
                                }
@@ -1820,10 +1819,10 @@ static void R_SetupShader_SetPermutationSoft(unsigned int mode, unsigned int per
 void R_GLSL_Restart_f(void)
 {
        unsigned int i, limit;
-       if (glslshaderstring && glslshaderstring != builtinshaderstring)
+       if (glslshaderstring)
                Mem_Free(glslshaderstring);
        glslshaderstring = NULL;
-       if (hlslshaderstring && hlslshaderstring != builtinhlslshaderstring)
+       if (hlslshaderstring)
                Mem_Free(hlslshaderstring);
        hlslshaderstring = NULL;
        switch(vid.renderpath)
@@ -1883,42 +1882,44 @@ void R_GLSL_Restart_f(void)
 
 static void R_GLSL_DumpShader_f(void)
 {
-       int i;
+       int i, language, mode, dupe;
+       char *text;
+       shadermodeinfo_t *modeinfo;
        qfile_t *file;
 
-       file = FS_OpenRealFile("glsl/default.glsl", "w", false);
-       if (file)
+       for (language = 0;language < 2;language++)
        {
-               FS_Print(file, "/* The engine may define the following macros:\n");
-               FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n");
-               for (i = 0;i < SHADERMODE_COUNT;i++)
-                       FS_Print(file, glslshadermodeinfo[i].pretext);
-               for (i = 0;i < SHADERPERMUTATION_COUNT;i++)
-                       FS_Print(file, shaderpermutationinfo[i].pretext);
-               FS_Print(file, "*/\n");
-               FS_Print(file, builtinshaderstring);
-               FS_Close(file);
-               Con_Printf("glsl/default.glsl written\n");
-       }
-       else
-               Con_Printf("failed to write to glsl/default.glsl\n");
-
-       file = FS_OpenRealFile("hlsl/default.hlsl", "w", false);
-       if (file)
-       {
-               FS_Print(file, "/* The engine may define the following macros:\n");
-               FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n");
-               for (i = 0;i < SHADERMODE_COUNT;i++)
-                       FS_Print(file, hlslshadermodeinfo[i].pretext);
-               for (i = 0;i < SHADERPERMUTATION_COUNT;i++)
-                       FS_Print(file, shaderpermutationinfo[i].pretext);
-               FS_Print(file, "*/\n");
-               FS_Print(file, builtinhlslshaderstring);
-               FS_Close(file);
-               Con_Printf("hlsl/default.hlsl written\n");
+               modeinfo = (language == 0 ? glslshadermodeinfo : hlslshadermodeinfo);
+               for (mode = 0;mode < SHADERMODE_COUNT;mode++)
+               {
+                       // don't dump the same file multiple times (most or all shaders come from the same file)
+                       for (dupe = mode - 1;dupe >= 0;dupe--)
+                               if (!strcmp(modeinfo[mode].filename, modeinfo[dupe].filename))
+                                       break;
+                       if (dupe >= 0)
+                               continue;
+                       text = R_GetShaderText(modeinfo[mode].filename, false, true);
+                       if (!text)
+                               continue;
+                       file = FS_OpenRealFile(modeinfo[mode].filename, "w", false);
+                       if (file)
+                       {
+                               FS_Print(file, "/* The engine may define the following macros:\n");
+                               FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n");
+                               for (i = 0;i < SHADERMODE_COUNT;i++)
+                                       FS_Print(file, modeinfo[i].pretext);
+                               for (i = 0;i < SHADERPERMUTATION_COUNT;i++)
+                                       FS_Print(file, shaderpermutationinfo[i].pretext);
+                               FS_Print(file, "*/\n");
+                               FS_Print(file, text);
+                               FS_Close(file);
+                               Con_Printf("%s written\n", modeinfo[mode].filename);
+                       }
+                       else
+                               Con_Printf("failed to write to %s\n", modeinfo[mode].filename);
+                       Mem_Free(text);
+               }
        }
-       else
-               Con_Printf("failed to write to hlsl/default.hlsl\n");
 }
 
 void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean usegamma, qboolean notrippy, qboolean suppresstexalpha)
@@ -1965,8 +1966,10 @@ void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemod
        case RENDERPATH_GL20:
        case RENDERPATH_GLES2:
                R_SetupShader_SetPermutationGLSL(SHADERMODE_GENERIC, permutation);
-               R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first );
-               R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second);
+               if (r_glsl_permutation->tex_Texture_First >= 0)
+                       R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first );
+               if (r_glsl_permutation->tex_Texture_Second >= 0)
+                       R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second);
                if (r_glsl_permutation->tex_Texture_GammaRamps >= 0)
                        R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps);
                break;
@@ -1974,12 +1977,18 @@ void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemod
        case RENDERPATH_GLES1:
                R_Mesh_TexBind(0, first );
                R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1);
+               R_Mesh_TexMatrix(0, NULL);
                R_Mesh_TexBind(1, second);
                if (second)
+               {
                        R_Mesh_TexCombine(1, texturemode, texturemode, rgbscale, 1);
+                       R_Mesh_TexMatrix(1, NULL);
+               }
                break;
        case RENDERPATH_GL11:
                R_Mesh_TexBind(0, first );
+               R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1);
+               R_Mesh_TexMatrix(0, NULL);
                break;
        case RENDERPATH_SOFT:
                R_SetupShader_SetPermutationSoft(SHADERMODE_GENERIC, permutation);
@@ -2037,41 +2046,6 @@ void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean
        }
 }
 
-void R_SetupShader_ShowDepth(qboolean notrippy)
-{
-       int permutation = 0;
-       if (r_trippy.integer && !notrippy)
-               permutation |= SHADERPERMUTATION_TRIPPY;
-       if (vid.allowalphatocoverage)
-               GL_AlphaToCoverage(false);
-       switch (vid.renderpath)
-       {
-       case RENDERPATH_D3D9:
-#ifdef SUPPORTHLSL
-               R_SetupShader_SetPermutationHLSL(SHADERMODE_SHOWDEPTH, permutation);
-#endif
-               break;
-       case RENDERPATH_D3D10:
-               Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
-               break;
-       case RENDERPATH_D3D11:
-               Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__);
-               break;
-       case RENDERPATH_GL20:
-       case RENDERPATH_GLES2:
-               R_SetupShader_SetPermutationGLSL(SHADERMODE_SHOWDEPTH, permutation);
-               break;
-       case RENDERPATH_GL13:
-       case RENDERPATH_GLES1:
-               break;
-       case RENDERPATH_GL11:
-               break;
-       case RENDERPATH_SOFT:
-               R_SetupShader_SetPermutationSoft(SHADERMODE_SHOWDEPTH, permutation);
-               break;
-       }
-}
-
 extern qboolean r_shadow_usingdeferredprepass;
 extern rtexture_t *r_shadow_attenuationgradienttexture;
 extern rtexture_t *r_shadow_attenuation2dtexture;
@@ -4578,8 +4552,8 @@ void *R_FrameData_Alloc(size_t size)
        r_framedata_mem->current += size;
 
        // count the usage for stats
-       r_refdef.stats.framedatacurrent = max(r_refdef.stats.framedatacurrent, (int)r_framedata_mem->current);
-       r_refdef.stats.framedatasize = max(r_refdef.stats.framedatasize, (int)r_framedata_mem->size);
+       r_refdef.stats[r_stat_framedatacurrent] = max(r_refdef.stats[r_stat_framedatacurrent], (int)r_framedata_mem->current);
+       r_refdef.stats[r_stat_framedatasize] = max(r_refdef.stats[r_stat_framedatasize], (int)r_framedata_mem->size);
 
        return (void *)data;
 }
@@ -4651,6 +4625,9 @@ static void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numver
        // TODO: upload vertex3f buffer?
        if (ent->animcache_vertexmesh)
        {
+               r_refdef.stats[r_stat_animcache_vertexmesh_count] += 1;
+               r_refdef.stats[r_stat_animcache_vertexmesh_vertices] += numvertices;
+               r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices] = max(r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices], numvertices);
                memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.vertexmesh, sizeof(r_vertexmesh_t)*numvertices);
                for (i = 0;i < numvertices;i++)
                        memcpy(ent->animcache_vertexmesh[i].vertex3f, ent->animcache_vertex3f + 3*i, sizeof(float[3]));
@@ -4672,9 +4649,31 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
        dp_model_t *model = ent->model;
        int numvertices;
 
-       // cache skeletal animation data first (primarily for gpu-skinning)
-       if (!ent->animcache_skeletaltransform3x4 && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub)
+       // see if this ent is worth caching
+       if (!model || !model->Draw || !model->AnimateVertices)
+               return false;
+       // nothing to cache if it contains no animations and has no skeleton
+       if (!model->surfmesh.isanimated && !(model->num_bones && ent->skeleton && ent->skeleton->relativetransforms))
+               return false;
+       // see if it is already cached for gpuskeletal
+       if (ent->animcache_skeletaltransform3x4)
+               return false;
+       // see if it is already cached as a mesh
+       if (ent->animcache_vertex3f)
        {
+               // check if we need to add normals or tangents
+               if (ent->animcache_normal3f)
+                       wantnormals = false;
+               if (ent->animcache_svector3f)
+                       wanttangents = false;
+               if (!wantnormals && !wanttangents)
+                       return false;
+       }
+
+       // check which kind of cache we need to generate
+       if (r_gpuskeletal && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub)
+       {
+               // cache the skeleton so the vertex shader can use it
                int i;
                int blends;
                const skeleton_t *skeleton = ent->skeleton;
@@ -4682,7 +4681,10 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
                float *boneposerelative;
                float m[12];
                static float bonepose[256][12];
-               ent->animcache_skeletaltransform3x4 = R_FrameData_Alloc(sizeof(float[3][4]) * model->num_bones);
+               r_refdef.stats[r_stat_animcache_skeletal_count] += 1;
+               r_refdef.stats[r_stat_animcache_skeletal_bones] += model->num_bones;
+               r_refdef.stats[r_stat_animcache_skeletal_maxbones] = max(r_refdef.stats[r_stat_animcache_skeletal_maxbones], model->num_bones);
+               ent->animcache_skeletaltransform3x4 = (float *)R_FrameData_Alloc(sizeof(float[3][4]) * model->num_bones);
                boneposerelative = ent->animcache_skeletaltransform3x4;
                if (skeleton && !skeleton->relativetransforms)
                        skeleton = NULL;
@@ -4770,56 +4772,30 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
                        }
                }
        }
-
-       // see if it's already cached this frame
-       if (ent->animcache_vertex3f)
+       else if (ent->animcache_vertex3f)
        {
-               // add normals/tangents if needed (this only happens with multiple views, reflections, cameras, etc)
+               // mesh was already cached but we may need to add normals/tangents
+               // (this only happens with multiple views, reflections, cameras, etc)
                if (wantnormals || wanttangents)
                {
-                       if (ent->animcache_normal3f)
-                               wantnormals = false;
-                       if (ent->animcache_svector3f)
-                               wanttangents = false;
-                       if (wantnormals || wanttangents)
+                       numvertices = model->surfmesh.num_vertices;
+                       if (wantnormals)
+                               ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
+                       if (wanttangents)
                        {
-                               numvertices = model->surfmesh.num_vertices;
-                               if (wantnormals)
-                                       ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                               if (wanttangents)
-                               {
-                                       ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                                       ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                               }
-                               model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL);
-                               R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices);
+                               ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
+                               ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
                        }
+                       model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL);
+                       R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices);
+                       r_refdef.stats[r_stat_animcache_shade_count] += 1;
+                       r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices;
+                       r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices);
                }
        }
        else
        {
-               // see if this ent is worth caching
-               if (!model || !model->Draw || !model->surfmesh.isanimated || !model->AnimateVertices)
-                       return false;
-               // skip entity if the shader backend has a cheaper way
-               if (model->surfmesh.data_skeletalindex4ub && r_glsl_skeletal.integer)
-               {
-                       switch (vid.renderpath)
-                       {
-                       case RENDERPATH_GL20:
-                               return false;
-                       case RENDERPATH_GL11:
-                       case RENDERPATH_GL13:
-                       case RENDERPATH_GLES1:
-                       case RENDERPATH_GLES2:
-                       case RENDERPATH_D3D9:
-                       case RENDERPATH_D3D10:
-                       case RENDERPATH_D3D11:
-                       case RENDERPATH_SOFT:
-                               break;
-                       }
-               }
-               // get some memory for this entity and generate mesh data
+               // generate mesh cache
                numvertices = model->surfmesh.num_vertices;
                ent->animcache_vertex3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
                if (wantnormals)
@@ -4831,6 +4807,15 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
                }
                model->AnimateVertices(model, ent->frameblend, ent->skeleton, ent->animcache_vertex3f, ent->animcache_normal3f, ent->animcache_svector3f, ent->animcache_tvector3f);
                R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices);
+               if (wantnormals || wanttangents)
+               {
+                       r_refdef.stats[r_stat_animcache_shade_count] += 1;
+                       r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices;
+                       r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices);
+               }
+               r_refdef.stats[r_stat_animcache_shape_count] += 1;
+               r_refdef.stats[r_stat_animcache_shape_vertices] += numvertices;
+               r_refdef.stats[r_stat_animcache_shape_maxvertices] = max(r_refdef.stats[r_stat_animcache_shape_maxvertices], numvertices);
        }
        return true;
 }
@@ -5117,7 +5102,7 @@ static void R_DrawModels(void)
                if (!r_refdef.viewcache.entityvisible[i])
                        continue;
                ent = r_refdef.scene.entities[i];
-               r_refdef.stats.entities++;
+               r_refdef.stats[r_stat_entities]++;
                /*
                if (ent->model && !strncmp(ent->model->name, "models/proto_", 13))
                {
@@ -6378,14 +6363,14 @@ static void R_Bloom_MakeTexture(void)
        rtexture_t *intex;
        float colorscale = r_bloom_colorscale.value;
 
-       r_refdef.stats.bloom++;
+       r_refdef.stats[r_stat_bloom]++;
     
 #if 0
     // this copy is unnecessary since it happens in R_BlendView already
        if (!r_fb.fbo)
        {
                R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
-               r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+               r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
        }
 #endif
 
@@ -6417,14 +6402,14 @@ static void R_Bloom_MakeTexture(void)
        // TODO: do boxfilter scale-down in shader?
        R_SetupShader_Generic(r_fb.colortexture, NULL, GL_MODULATE, 1, false, true, true);
        R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-       r_refdef.stats.bloom_drawpixels += r_fb.bloomwidth * r_fb.bloomheight;
+       r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
 
        // we now have a properly scaled bloom image
        if (!r_fb.bloomfbo[r_fb.bloomindex])
        {
                // copy it into the bloom texture
                R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
-               r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+               r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
        }
 
        // multiply bloom image by itself as many times as desired
@@ -6450,13 +6435,13 @@ static void R_Bloom_MakeTexture(void)
                R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.bloomtexcoord2f);
                R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false);
                R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-               r_refdef.stats.bloom_drawpixels += r_fb.bloomwidth * r_fb.bloomheight;
+               r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
 
                if (!r_fb.bloomfbo[r_fb.bloomindex])
                {
                        // copy the darkened image to a texture
                        R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
-                       r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+                       r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
                }
        }
 
@@ -6502,7 +6487,7 @@ static void R_Bloom_MakeTexture(void)
                        GL_Color(r, r, r, 1);
                        R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.offsettexcoord2f);
                        R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-                       r_refdef.stats.bloom_drawpixels += r_fb.bloomwidth * r_fb.bloomheight;
+                       r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight;
                        GL_BlendFunc(GL_ONE, GL_ONE);
                }
 
@@ -6510,7 +6495,7 @@ static void R_Bloom_MakeTexture(void)
                {
                        // copy the vertically or horizontally blurred bloom view to a texture
                        R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height);
-                       r_refdef.stats.bloom_copypixels += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
+                       r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height;
                }
        }
 }
@@ -6542,7 +6527,7 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
                        if (!r_fb.fbo)
                        {
                                R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
-                               r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+                               r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
                        }
 
                        if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0) && r_fb.ghosttexture)
@@ -6608,7 +6593,7 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
                                        }
                                        R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true, true);
                                        R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-                                       r_refdef.stats.bloom_drawpixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+                                       r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
                                }
 
                                // updates old view angles for next pass
@@ -6616,7 +6601,7 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
 
                                // copy view into the ghost texture
                                R_Mesh_CopyToTexture(r_fb.ghosttexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
-                               r_refdef.stats.bloom_copypixels += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
+                               r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height;
                                r_fb.ghosttexture_valid = true;
                        }
                }
@@ -6724,7 +6709,7 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
                        break;
                }
                R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0);
-               r_refdef.stats.bloom_drawpixels += r_refdef.view.width * r_refdef.view.height;
+               r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.width * r_refdef.view.height;
                break;
        case RENDERPATH_GL11:
        case RENDERPATH_GL13:
@@ -6872,9 +6857,11 @@ void R_UpdateVariables(void)
                r_refdef.lightmapintensity = 0;
        }
 
+       r_gpuskeletal = false;
        switch(vid.renderpath)
        {
        case RENDERPATH_GL20:
+               r_gpuskeletal = r_glsl_skeletal.integer && !r_showsurfaces.integer; // FIXME add r_showsurfaces support to GLSL skeletal!
        case RENDERPATH_D3D9:
        case RENDERPATH_D3D10:
        case RENDERPATH_D3D11:
@@ -7159,7 +7146,7 @@ void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture)
        if (r_timereport_active)
                R_TimeReport("beginscene");
 
-       r_refdef.stats.renders++;
+       r_refdef.stats[r_stat_renders]++;
 
        R_UpdateFog();
 
@@ -8399,6 +8386,10 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        {
                if (ent->animcache_vertex3f)
                {
+                       r_refdef.stats[r_stat_batch_entitycache_count]++;
+                       r_refdef.stats[r_stat_batch_entitycache_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entitycache_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entitycache_triangles] += model->surfmesh.num_triangles;
                        rsurface.modelvertex3f = ent->animcache_vertex3f;
                        rsurface.modelsvector3f = wanttangents ? ent->animcache_svector3f : NULL;
                        rsurface.modeltvector3f = wanttangents ? ent->animcache_tvector3f : NULL;
@@ -8409,6 +8400,10 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                }
                else if (wanttangents)
                {
+                       r_refdef.stats[r_stat_batch_entityanimate_count]++;
+                       r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles;
                        rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        rsurface.modelsvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        rsurface.modeltvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
@@ -8420,6 +8415,10 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                }
                else if (wantnormals)
                {
+                       r_refdef.stats[r_stat_batch_entityanimate_count]++;
+                       r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles;
                        rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        rsurface.modelsvector3f = NULL;
                        rsurface.modeltvector3f = NULL;
@@ -8431,6 +8430,10 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                }
                else
                {
+                       r_refdef.stats[r_stat_batch_entityanimate_count]++;
+                       r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles;
                        rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        rsurface.modelsvector3f = NULL;
                        rsurface.modeltvector3f = NULL;
@@ -8452,6 +8455,20 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        }
        else
        {
+               if (rsurface.entityskeletaltransform3x4)
+               {
+                       r_refdef.stats[r_stat_batch_entityskeletal_count]++;
+                       r_refdef.stats[r_stat_batch_entityskeletal_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entityskeletal_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entityskeletal_triangles] += model->surfmesh.num_triangles;
+               }
+               else
+               {
+                       r_refdef.stats[r_stat_batch_entitystatic_count]++;
+                       r_refdef.stats[r_stat_batch_entitystatic_surfaces] += model->num_surfaces;
+                       r_refdef.stats[r_stat_batch_entitystatic_vertices] += model->surfmesh.num_vertices;
+                       r_refdef.stats[r_stat_batch_entitystatic_triangles] += model->surfmesh.num_triangles;
+               }
                rsurface.modelvertex3f  = model->surfmesh.data_vertex3f;
                rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
                rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f;
@@ -8576,6 +8593,10 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
        rsurface.basepolygonoffset = r_refdef.polygonoffset;
        rsurface.entityskeletaltransform3x4 = NULL;
        rsurface.entityskeletalnumtransforms = 0;
+       r_refdef.stats[r_stat_batch_entitycustom_count]++;
+       r_refdef.stats[r_stat_batch_entitycustom_surfaces] += 1;
+       r_refdef.stats[r_stat_batch_entitycustom_vertices] += rsurface.modelnumvertices;
+       r_refdef.stats[r_stat_batch_entitycustom_triangles] += rsurface.modelnumtriangles;
        if (wanttangents)
        {
                rsurface.modelvertex3f = (float *)vertex3f;
@@ -8748,6 +8769,7 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        int surfacefirstvertex;
        int surfaceendvertex;
        int surfacenumvertices;
+       int batchnumsurfaces = texturenumsurfaces;
        int batchnumvertices;
        int batchnumtriangles;
        int needsupdate;
@@ -8789,6 +8811,13 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                batchnumtriangles += surfacenumtriangles;
        }
 
+       r_refdef.stats[r_stat_batch_batches]++;
+       if (gaps)
+               r_refdef.stats[r_stat_batch_withgaps]++;
+       r_refdef.stats[r_stat_batch_surfaces] += batchnumsurfaces;
+       r_refdef.stats[r_stat_batch_vertices] += batchnumvertices;
+       r_refdef.stats[r_stat_batch_triangles] += batchnumtriangles;
+
        // we now know the vertex range used, and if there are any gaps in it
        rsurface.batchfirstvertex = firstvertex;
        rsurface.batchnumvertices = endvertex - firstvertex;
@@ -8804,11 +8833,27 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
 
        // a cvar to force the dynamic vertex path to be taken, for debugging
        if (r_batch_debugdynamicvertexpath.integer)
+       {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_cvar] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_cvar] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_cvar] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_cvar] += batchnumtriangles;
+               }
                dynamicvertex = true;
+       }
 
        // if there is a chance of animated vertex colors, it's a dynamic batch
        if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo)
        {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_lightmapvertex] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_lightmapvertex] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_lightmapvertex] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_lightmapvertex] += batchnumtriangles;
+               }
                dynamicvertex = true;
                needsupdate |= BATCHNEED_VERTEXMESH_VERTEXCOLOR;
        }
@@ -8830,16 +8875,37 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                case Q3DEFORM_NONE:
                        break;
                case Q3DEFORM_AUTOSPRITE:
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_AUTOSPRITE2:
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite2] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite2] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite2] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite2] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_NORMAL:
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_normal] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_normal] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_normal] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_normal] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
@@ -8847,11 +8913,25 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                case Q3DEFORM_WAVE:
                        if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms))
                                break; // if wavefunc is a nop, ignore this transform
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_wave] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_wave] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_wave] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_wave] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_BULGE:
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_bulge] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_bulge] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_bulge] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_bulge] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
@@ -8859,6 +8939,13 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                case Q3DEFORM_MOVE:
                        if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms))
                                break; // if wavefunc is a nop, ignore this transform
+                       if (!dynamicvertex)
+                       {
+                               r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_move] += 1;
+                               r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_move] += batchnumsurfaces;
+                               r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_move] += batchnumvertices;
+                               r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_move] += batchnumtriangles;
+                       }
                        dynamicvertex = true;
                        batchneed |= BATCHNEED_ARRAY_VERTEX;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX;
@@ -8871,16 +8958,37 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        case Q3TCGEN_TEXTURE:
                break;
        case Q3TCGEN_LIGHTMAP:
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_lightmap] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_lightmap] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_lightmap] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_lightmap] += batchnumtriangles;
+               }
                dynamicvertex = true;
                batchneed |= BATCHNEED_ARRAY_LIGHTMAP;
                needsupdate |= BATCHNEED_VERTEXMESH_LIGHTMAP;
                break;
        case Q3TCGEN_VECTOR:
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_vector] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_vector] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_vector] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_vector] += batchnumtriangles;
+               }
                dynamicvertex = true;
                batchneed |= BATCHNEED_ARRAY_VERTEX;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
                break;
        case Q3TCGEN_ENVIRONMENT:
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_environment] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_environment] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_environment] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_environment] += batchnumtriangles;
+               }
                dynamicvertex = true;
                batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
@@ -8888,6 +8996,13 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        }
        if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT)
        {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_tcmod_turbulent] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcmod_turbulent] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcmod_turbulent] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcmod_turbulent] += batchnumtriangles;
+               }
                dynamicvertex = true;
                batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
@@ -8895,20 +9010,36 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
 
        if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)))
        {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles;
+               }
                dynamicvertex = true;
                needsupdate |= (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP));
        }
 
        // when the model data has no vertex buffer (dynamic mesh), we need to
        // eliminate gaps
-       if (vid.useinterleavedarrays ? !rsurface.modelvertexmeshbuffer : !rsurface.modelvertex3f_vertexbuffer)
+       if (vid.useinterleavedarrays && !rsurface.modelvertexmeshbuffer)
                batchneed |= BATCHNEED_NOGAPS;
 
        // the caller can specify BATCHNEED_NOGAPS to force a batch with
        // firstvertex = 0 and endvertex = numvertices (no gaps, no firstvertex),
        // we ensure this by treating the vertex batch as dynamic...
        if ((batchneed & BATCHNEED_NOGAPS) && (gaps || firstvertex > 0))
+       {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_nogaps] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_nogaps] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_nogaps] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_nogaps] += batchnumtriangles;
+               }
                dynamicvertex = true;
+       }
 
        if (dynamicvertex)
        {
@@ -8924,11 +9055,29 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
 
        // if needsupdate, we have to do a dynamic vertex batch for sure
        if (needsupdate & batchneed)
+       {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_derived] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_derived] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_derived] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_derived] += batchnumtriangles;
+               }
                dynamicvertex = true;
+       }
 
        // see if we need to build vertexmesh from arrays
        if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)))
+       {
+               if (!dynamicvertex)
+               {
+                       r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1;
+                       r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles;
+               }
                dynamicvertex = true;
+       }
 
        // if we're going to have to apply the skeletal transform manually, we need to batch the skeletal data
        if (dynamicvertex && rsurface.entityskeletaltransform3x4)
@@ -8999,6 +9148,10 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                // otherwise use the original static buffer with an appropriate offset
                if (gaps)
                {
+                       r_refdef.stats[r_stat_batch_copytriangles_batches] += 1;
+                       r_refdef.stats[r_stat_batch_copytriangles_surfaces] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_copytriangles_vertices] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_copytriangles_triangles] += batchnumtriangles;
                        if ((batchneed & BATCHNEED_ALLOWMULTIDRAW) && r_batch_multidraw.integer && batchnumtriangles >= r_batch_multidraw_mintriangles.integer)
                        {
                                rsurface.batchmultidraw = true;
@@ -9030,6 +9183,13 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                                        rsurface.batchelement3s[i] = rsurface.batchelement3i[i];
                        }
                }
+               else
+               {
+                       r_refdef.stats[r_stat_batch_fast_batches] += 1;
+                       r_refdef.stats[r_stat_batch_fast_surfaces] += batchnumsurfaces;
+                       r_refdef.stats[r_stat_batch_fast_vertices] += batchnumvertices;
+                       r_refdef.stats[r_stat_batch_fast_triangles] += batchnumtriangles;
+               }
                return;
        }
 
@@ -9037,6 +9197,10 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        // we only directly handle separate array data in this case and then
        // generate interleaved data if needed...
        rsurface.batchgeneratedvertex = true;
+       r_refdef.stats[r_stat_batch_dynamic_batches] += 1;
+       r_refdef.stats[r_stat_batch_dynamic_surfaces] += batchnumsurfaces;
+       r_refdef.stats[r_stat_batch_dynamic_vertices] += batchnumvertices;
+       r_refdef.stats[r_stat_batch_dynamic_triangles] += batchnumtriangles;
 
        // now copy the vertex data into a combined array and make an index array
        // (this is what Quake3 does all the time)
@@ -9214,6 +9378,10 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                float w[4];
                float m[3][4], n[3][4];
                float tp[3], ts[3], tt[3], tn[3];
+               r_refdef.stats[r_stat_batch_dynamicskeletal_batches] += 1;
+               r_refdef.stats[r_stat_batch_dynamicskeletal_surfaces] += batchnumsurfaces;
+               r_refdef.stats[r_stat_batch_dynamicskeletal_vertices] += batchnumvertices;
+               r_refdef.stats[r_stat_batch_dynamicskeletal_triangles] += batchnumtriangles;
                si = rsurface.batchskeletalindex4ub;
                sw = rsurface.batchskeletalweight4ub;
                vp = rsurface.batchvertex3f;
@@ -10000,6 +10168,8 @@ static void RSurf_DrawBatch_GL11_Lightmap(float r, float g, float b, float a, qb
        R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset);
        GL_Color(r, g, b, a);
        R_Mesh_TexBind(0, rsurface.lightmaptexture);
+       R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1);
+       R_Mesh_TexMatrix(0, NULL);
        RSurf_DrawBatch();
 }
 
@@ -11749,7 +11919,7 @@ static void R_DrawModelDecals_Entity(entity_render_t *ent)
 
        if (numtris > 0)
        {
-               r_refdef.stats.drawndecals += numtris;
+               r_refdef.stats[r_stat_drawndecals] += numtris;
 
                // now render the decals all at once
                // (this assumes they all use one particle font texture!)
@@ -11787,7 +11957,7 @@ static void R_DrawModelDecals(void)
        for (i = 0;i < r_refdef.scene.numentities;i++)
                numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals;
 
-       r_refdef.stats.totaldecals += numdecals;
+       r_refdef.stats[r_stat_totaldecals] += numdecals;
 
        if (r_showsurfaces.integer)
                return;
@@ -12107,9 +12277,9 @@ void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean dep
        // add to stats if desired
        if (r_speeds.integer && !skysurfaces && !depthonly)
        {
-               r_refdef.stats.world_surfaces += numsurfacelist;
+               r_refdef.stats[r_stat_world_surfaces] += numsurfacelist;
                for (j = 0;j < numsurfacelist;j++)
-                       r_refdef.stats.world_triangles += r_surfacelist[j]->num_triangles;
+                       r_refdef.stats[r_stat_world_triangles] += r_surfacelist[j]->num_triangles;
        }
 
        rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity
@@ -12243,9 +12413,9 @@ void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean wr
        // add to stats if desired
        if (r_speeds.integer && !skysurfaces && !depthonly)
        {
-               r_refdef.stats.entities_surfaces += numsurfacelist;
+               r_refdef.stats[r_stat_entities_surfaces] += numsurfacelist;
                for (j = 0;j < numsurfacelist;j++)
-                       r_refdef.stats.entities_triangles += r_surfacelist[j]->num_triangles;
+                       r_refdef.stats[r_stat_entities_triangles] += r_surfacelist[j]->num_triangles;
        }
 
        rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity