]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - gl_rmain.c
Build in subdirs
[xonotic/darkplaces.git] / gl_rmain.c
index 5b816ae6a3f59cf9f1c6c3c82982e8f2471d2756..755925c9f993eb58409901c747a68fdcf1a187c7 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
@@ -229,6 +230,7 @@ cvar_t r_test = {0, "r_test", "0", "internal development use only, leave it alon
 cvar_t r_batch_multidraw = {CVAR_SAVE, "r_batch_multidraw", "1", "issue multiple glDrawElements calls when rendering a batch of surfaces with the same texture (otherwise the index data is copied to make it one draw)"};
 cvar_t r_batch_multidraw_mintriangles = {CVAR_SAVE, "r_batch_multidraw_mintriangles", "0", "minimum number of triangles to activate multidraw path (copying small groups of triangles may be faster)"};
 cvar_t r_batch_debugdynamicvertexpath = {CVAR_SAVE, "r_batch_debugdynamicvertexpath", "0", "force the dynamic batching code path for debugging purposes"};
+cvar_t r_batch_dynamicbuffer = {CVAR_SAVE, "r_batch_dynamicbuffer", "0", "use vertex/index buffers for drawing dynamic and copytriangles batches"};
 
 cvar_t r_glsl_saturation = {CVAR_SAVE, "r_glsl_saturation", "1", "saturation multiplier (only working in glsl!)"};
 cvar_t r_glsl_saturation_redcompensate = {CVAR_SAVE, "r_glsl_saturation_redcompensate", "0", "a 'vampire sight' addition to desaturation effect, does compensation for red color, r_glsl_restart is required"};
@@ -236,6 +238,13 @@ cvar_t r_glsl_saturation_redcompensate = {CVAR_SAVE, "r_glsl_saturation_redcompe
 cvar_t r_glsl_vertextextureblend_usebothalphas = {CVAR_SAVE, "r_glsl_vertextextureblend_usebothalphas", "0", "use both alpha layers on vertex blended surfaces, each alpha layer sets amount of 'blend leak' on another layer, requires mod_q3shader_force_terrain_alphaflag on."};
 
 cvar_t r_framedatasize = {CVAR_SAVE, "r_framedatasize", "0.5", "size of renderer data cache used during one frame (for skeletal animation caching, light processing, etc)"};
+cvar_t r_buffermegs[R_BUFFERDATA_COUNT] =
+{
+       {CVAR_SAVE, "r_buffermegs_vertex", "4", "vertex buffer size for one frame"},
+       {CVAR_SAVE, "r_buffermegs_index16", "1", "index buffer size for one frame (16bit indices)"},
+       {CVAR_SAVE, "r_buffermegs_index32", "1", "index buffer size for one frame (32bit indices)"},
+       {CVAR_SAVE, "r_buffermegs_uniform", "0.25", "uniform buffer size for one frame"},
+};
 
 extern cvar_t v_glslgamma;
 extern cvar_t v_glslgamma_2d;
@@ -247,6 +256,8 @@ r_framebufferstate_t r_fb;
 /// shadow volume bsp struct with automatically growing nodes buffer
 svbsp_t r_svbsp;
 
+int r_uniformbufferalignment = 32; // dynamically updated to match GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT
+
 rtexture_t *r_texture_blanknormalmap;
 rtexture_t *r_texture_white;
 rtexture_t *r_texture_grey128;
@@ -843,6 +854,10 @@ typedef struct r_glsl_permutation_s
        int loc_NormalmapScrollBlend;
        int loc_BounceGridMatrix;
        int loc_BounceGridIntensity;
+       /// uniform block bindings
+       int ubibind_Skeletal_Transform12_UniformBlock;
+       /// uniform block indices
+       int ubiloc_Skeletal_Transform12_UniformBlock;
 }
 r_glsl_permutation_t;
 
@@ -984,7 +999,7 @@ static char *R_ShaderStrCat(const char **strings)
        for (p = strings;(t = *p);p++)
                len += strlen(t);
        len++;
-       s = string = Mem_Alloc(r_main_mempool, len);
+       s = string = (char *)Mem_Alloc(r_main_mempool, len);
        len = 0;
        for (p = strings;(t = *p);p++)
        {
@@ -1050,6 +1065,7 @@ static char *R_GetShaderText(const char *filename, qboolean printfromdisknotice,
 static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode, unsigned int permutation)
 {
        int i;
+       int ubibind;
        int sampler;
        shadermodeinfo_t *modeinfo = glslshadermodeinfo + mode;
        char *sourcestring;
@@ -1071,8 +1087,18 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
 
        strlcat(permutationname, modeinfo->filename, sizeof(permutationname));
 
+       // we need 140 for r_glsl_skeletal (GL_ARB_uniform_buffer_object)
+       if(vid.support.glshaderversion >= 140)
+       {
+               vertstrings_list[vertstrings_count++] = "#version 140\n";
+               geomstrings_list[geomstrings_count++] = "#version 140\n";
+               fragstrings_list[fragstrings_count++] = "#version 140\n";
+               vertstrings_list[vertstrings_count++] = "#define GLSL140\n";
+               geomstrings_list[geomstrings_count++] = "#define GLSL140\n";
+               fragstrings_list[fragstrings_count++] = "#define GLSL140\n";
+       }
        // if we can do #version 130, we should (this improves quality of offset/reliefmapping thanks to textureGrad)
-       if(vid.support.gl20shaders130)
+       else if(vid.support.glshaderversion >= 130)
        {
                vertstrings_list[vertstrings_count++] = "#version 130\n";
                geomstrings_list[geomstrings_count++] = "#version 130\n";
@@ -1137,6 +1163,26 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                // look up all the uniform variable names we care about, so we don't
                // have to look them up every time we set them
 
+#if 0
+               // debugging aid
+               {
+                       GLint activeuniformindex = 0;
+                       GLint numactiveuniforms = 0;
+                       char uniformname[128];
+                       GLsizei uniformnamelength = 0;
+                       GLint uniformsize = 0;
+                       GLenum uniformtype = 0;
+                       memset(uniformname, 0, sizeof(uniformname));
+                       qglGetProgramiv(p->program, GL_ACTIVE_UNIFORMS, &numactiveuniforms);
+                       Con_Printf("Shader has %i uniforms\n", numactiveuniforms);
+                       for (activeuniformindex = 0;activeuniformindex < numactiveuniforms;activeuniformindex++)
+                       {
+                               qglGetActiveUniform(p->program, activeuniformindex, sizeof(uniformname) - 1, &uniformnamelength, &uniformsize, &uniformtype, uniformname);
+                               Con_Printf("Uniform %i name \"%s\" size %i type %i\n", (int)activeuniformindex, uniformname, (int)uniformsize, (int)uniformtype);
+                       }
+               }
+#endif
+
                p->loc_Texture_First              = qglGetUniformLocation(p->program, "Texture_First");
                p->loc_Texture_Second             = qglGetUniformLocation(p->program, "Texture_Second");
                p->loc_Texture_GammaRamps         = qglGetUniformLocation(p->program, "Texture_GammaRamps");
@@ -1205,7 +1251,6 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                p->loc_ShadowMap_Parameters       = qglGetUniformLocation(p->program, "ShadowMap_Parameters");
                p->loc_ShadowMap_TextureScale     = qglGetUniformLocation(p->program, "ShadowMap_TextureScale");
                p->loc_SpecularPower              = qglGetUniformLocation(p->program, "SpecularPower");
-               p->loc_Skeletal_Transform12       = qglGetUniformLocation(p->program, "Skeletal_Transform12");
                p->loc_UserVec1                   = qglGetUniformLocation(p->program, "UserVec1");
                p->loc_UserVec2                   = qglGetUniformLocation(p->program, "UserVec2");
                p->loc_UserVec3                   = qglGetUniformLocation(p->program, "UserVec3");
@@ -1254,6 +1299,7 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                p->tex_Texture_ReflectMask = -1;
                p->tex_Texture_ReflectCube = -1;
                p->tex_Texture_BounceGrid = -1;
+               // bind the texture samplers in use
                sampler = 0;
                if (p->loc_Texture_First           >= 0) {p->tex_Texture_First            = sampler;qglUniform1i(p->loc_Texture_First           , sampler);sampler++;}
                if (p->loc_Texture_Second          >= 0) {p->tex_Texture_Second           = sampler;qglUniform1i(p->loc_Texture_Second          , sampler);sampler++;}
@@ -1284,6 +1330,21 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                if (p->loc_Texture_ReflectMask     >= 0) {p->tex_Texture_ReflectMask      = sampler;qglUniform1i(p->loc_Texture_ReflectMask     , sampler);sampler++;}
                if (p->loc_Texture_ReflectCube     >= 0) {p->tex_Texture_ReflectCube      = sampler;qglUniform1i(p->loc_Texture_ReflectCube     , sampler);sampler++;}
                if (p->loc_Texture_BounceGrid      >= 0) {p->tex_Texture_BounceGrid       = sampler;qglUniform1i(p->loc_Texture_BounceGrid      , sampler);sampler++;}
+               // get the uniform block indices so we can bind them
+#ifndef USE_GLES2 /* FIXME: GLES3 only */
+               if (vid.support.arb_uniform_buffer_object)
+                       p->ubiloc_Skeletal_Transform12_UniformBlock = qglGetUniformBlockIndex(p->program, "Skeletal_Transform12_UniformBlock");
+               else
+#endif
+                       p->ubiloc_Skeletal_Transform12_UniformBlock = -1;
+               // clear the uniform block bindings
+               p->ubibind_Skeletal_Transform12_UniformBlock = -1;
+               // bind the uniform blocks in use
+               ubibind = 0;
+#ifndef USE_GLES2 /* FIXME: GLES3 only */
+               if (p->ubiloc_Skeletal_Transform12_UniformBlock >= 0) {p->ubibind_Skeletal_Transform12_UniformBlock = ubibind;qglUniformBlockBinding(p->program, p->ubiloc_Skeletal_Transform12_UniformBlock, ubibind);ubibind++;}
+#endif
+               // we're done compiling and setting up the shader, at least until it is used
                CHECKGLERROR
                Con_DPrintf("^5GLSL shader %s compiled (%i textures).\n", permutationname, sampler);
        }
@@ -1304,7 +1365,10 @@ static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int per
                if (!r_glsl_permutation->program)
                {
                        if (!r_glsl_permutation->compiled)
+                       {
+                               Con_DPrintf("Compiling shader mode %u permutation %u\n", mode, permutation);
                                R_GLSL_CompilePermutation(perm, mode, permutation);
+                       }
                        if (!r_glsl_permutation->program)
                        {
                                // remove features until we find a valid permutation
@@ -1337,6 +1401,7 @@ static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int per
        if (r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f);
        if (r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f);
        if (r_glsl_permutation->loc_ClientTime >= 0) qglUniform1f(r_glsl_permutation->loc_ClientTime, cl.time);
+       CHECKGLERROR
 }
 
 #ifdef SUPPORTD3D
@@ -1533,6 +1598,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;
@@ -1953,8 +2030,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;
@@ -1962,12 +2041,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);
@@ -2010,6 +2095,9 @@ void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean
        case RENDERPATH_GL20:
        case RENDERPATH_GLES2:
                R_SetupShader_SetPermutationGLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation);
+#ifndef USE_GLES2 /* FIXME: GLES3 only */
+               if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size);
+#endif
                break;
        case RENDERPATH_GL13:
        case RENDERPATH_GLES1:
@@ -2494,7 +2582,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
        case RENDERPATH_D3D9:
 #ifdef SUPPORTD3D
                RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
-               R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmeshbuffer);
+               R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset);
                R_SetupShader_SetPermutationHLSL(mode, permutation);
                Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);hlslPSSetParameter16f(D3DPSREGISTER_ModelToReflectCube, m16f);
                if (mode == SHADERMODE_LIGHTSOURCE)
@@ -2661,12 +2749,15 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                else
                {
                        RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | (rsurface.entityskeletaltransform3x4 ? BATCHNEED_VERTEXMESH_SKELETAL : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
-                       R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmeshbuffer);
+                       R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset);
                }
                // this has to be after RSurf_PrepareVerticesForBatch
-               if (rsurface.batchskeletaltransform3x4)
+               if (rsurface.batchskeletaltransform3x4buffer)
                        permutation |= SHADERPERMUTATION_SKELETAL;
                R_SetupShader_SetPermutationGLSL(mode, permutation);
+#ifndef USE_GLES2 /* FIXME: GLES3 only */
+               if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size);
+#endif
                if (r_glsl_permutation->loc_ModelToReflectCube >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToReflectCube, 1, false, m16f);}
                if (mode == SHADERMODE_LIGHTSOURCE)
                {
@@ -2807,8 +2898,6 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        }
                }
                if (r_glsl_permutation->tex_Texture_BounceGrid  >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_BounceGrid, r_shadow_bouncegridtexture);
-               if (r_glsl_permutation->loc_Skeletal_Transform12 >= 0 && rsurface.batchskeletalnumtransforms > 0)
-                       qglUniform4fv(r_glsl_permutation->loc_Skeletal_Transform12, rsurface.batchskeletalnumtransforms*3, rsurface.batchskeletaltransform3x4);
                CHECKGLERROR
                break;
        case RENDERPATH_GL11:
@@ -3931,6 +4020,7 @@ static void gl_main_start(void)
        r_texture_fogheighttexture = NULL;
        r_texture_gammaramps = NULL;
        r_texture_numcubemaps = 0;
+       r_uniformbufferalignment = 32;
 
        r_loaddds = r_texture_dds_load.integer != 0;
        r_savedds = vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && r_texture_dds_save.integer;
@@ -3949,7 +4039,11 @@ static void gl_main_start(void)
                r_loadnormalmap = true;
                r_loadgloss = true;
                r_loadfog = false;
-               break;
+#ifdef GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT
+               if (vid.support.arb_uniform_buffer_object)
+                       qglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &r_uniformbufferalignment);
+#endif
+                       break;
        case RENDERPATH_GL13:
        case RENDERPATH_GLES1:
                Cvar_SetValueQuick(&r_textureunits, vid.texunits);
@@ -3971,6 +4065,7 @@ static void gl_main_start(void)
 
        R_AnimCache_Free();
        R_FrameData_Reset();
+       R_BufferData_Reset();
 
        r_numqueries = 0;
        r_maxqueries = 0;
@@ -4016,12 +4111,34 @@ static void gl_main_start(void)
        r_texture_numcubemaps = 0;
 
        r_refdef.fogmasktable_density = 0;
+
+#ifdef __ANDROID__
+       // For Steelstorm Android
+       // FIXME CACHE the program and reload
+       // FIXME see possible combinations for SS:BR android
+       Con_DPrintf("Compiling most used shaders for SS:BR android... START\n");
+       R_SetupShader_SetPermutationGLSL(0, 12);
+       R_SetupShader_SetPermutationGLSL(0, 13);
+       R_SetupShader_SetPermutationGLSL(0, 8388621);
+       R_SetupShader_SetPermutationGLSL(3, 0);
+       R_SetupShader_SetPermutationGLSL(3, 2048);
+       R_SetupShader_SetPermutationGLSL(5, 0);
+       R_SetupShader_SetPermutationGLSL(5, 2);
+       R_SetupShader_SetPermutationGLSL(5, 2048);
+       R_SetupShader_SetPermutationGLSL(5, 8388608);
+       R_SetupShader_SetPermutationGLSL(11, 1);
+       R_SetupShader_SetPermutationGLSL(11, 2049);
+       R_SetupShader_SetPermutationGLSL(11, 8193);
+       R_SetupShader_SetPermutationGLSL(11, 10241);
+       Con_DPrintf("Compiling most used shaders for SS:BR android... END\n");
+#endif
 }
 
 static void gl_main_shutdown(void)
 {
        R_AnimCache_Free();
        R_FrameData_Reset();
+       R_BufferData_Reset();
 
        R_Main_FreeViewCache();
 
@@ -4032,7 +4149,7 @@ static void gl_main_shutdown(void)
        case RENDERPATH_GL20:
        case RENDERPATH_GLES1:
        case RENDERPATH_GLES2:
-#ifdef GL_SAMPLES_PASSED_ARB
+#if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2)
                if (r_maxqueries)
                        qglDeleteQueriesARB(r_maxqueries, r_queries);
 #endif
@@ -4115,10 +4232,12 @@ static void gl_main_newmap(void)
        R_Main_FreeViewCache();
 
        R_FrameData_Reset();
+       R_BufferData_Reset();
 }
 
 void GL_Main_Init(void)
 {
+       int i;
        r_main_mempool = Mem_AllocPool("Renderer", 0, NULL);
 
        Cmd_AddCommand("r_glsl_restart", R_GLSL_Restart_f, "unloads GLSL shaders, they will then be reloaded as needed");
@@ -4291,8 +4410,18 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_glsl_saturation_redcompensate);
        Cvar_RegisterVariable(&r_glsl_vertextextureblend_usebothalphas);
        Cvar_RegisterVariable(&r_framedatasize);
+       for (i = 0;i < R_BUFFERDATA_COUNT;i++)
+               Cvar_RegisterVariable(&r_buffermegs[i]);
+       Cvar_RegisterVariable(&r_batch_dynamicbuffer);
        if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE)
                Cvar_SetValue("r_fullbrights", 0);
+#ifdef DP_MOBILETOUCH
+       // GLES devices have terrible depth precision in general, so...
+       Cvar_SetValueQuick(&r_nearclip, 4);
+       Cvar_SetValueQuick(&r_farclip_base, 4096);
+       Cvar_SetValueQuick(&r_farclip_world, 0);
+       Cvar_SetValueQuick(&r_useinfinitefarclip, 0);
+#endif
        R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL);
 }
 
@@ -4476,12 +4605,12 @@ void R_FrameData_Reset(void)
        }
 }
 
-static void R_FrameData_Resize(void)
+static void R_FrameData_Resize(qboolean mustgrow)
 {
        size_t wantedsize;
        wantedsize = (size_t)(r_framedatasize.value * 1024*1024);
        wantedsize = bound(65536, wantedsize, 1000*1024*1024);
-       if (!r_framedata_mem || r_framedata_mem->wantedsize != wantedsize)
+       if (!r_framedata_mem || r_framedata_mem->wantedsize != wantedsize || mustgrow)
        {
                r_framedata_mem_t *newmem = (r_framedata_mem_t *)Mem_Alloc(r_main_mempool, wantedsize);
                newmem->wantedsize = wantedsize;
@@ -4496,7 +4625,7 @@ static void R_FrameData_Resize(void)
 
 void R_FrameData_NewFrame(void)
 {
-       R_FrameData_Resize();
+       R_FrameData_Resize(false);
        if (!r_framedata_mem)
                return;
        // if we ran out of space on the last frame, free the old memory now
@@ -4515,6 +4644,7 @@ void R_FrameData_NewFrame(void)
 void *R_FrameData_Alloc(size_t size)
 {
        void *data;
+       float newvalue;
 
        // align to 16 byte boundary - the data pointer is already aligned, so we
        // only need to ensure the size of every allocation is also aligned
@@ -4523,16 +4653,18 @@ void *R_FrameData_Alloc(size_t size)
        while (!r_framedata_mem || r_framedata_mem->current + size > r_framedata_mem->size)
        {
                // emergency - we ran out of space, allocate more memory
-               Cvar_SetValueQuick(&r_framedatasize, bound(0.25f, r_framedatasize.value * 2.0f, 128.0f));
-               R_FrameData_Resize();
+               newvalue = bound(0.25f, r_framedatasize.value * 2.0f, 256.0f);
+               // this might not be a growing it, but we'll allocate another buffer every time
+               Cvar_SetValueQuick(&r_framedatasize, newvalue);
+               R_FrameData_Resize(true);
        }
 
        data = r_framedata_mem->data + r_framedata_mem->current;
        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;
 }
@@ -4561,6 +4693,160 @@ void R_FrameData_ReturnToMark(void)
 
 //==================================================================================
 
+// avoid reusing the same buffer objects on consecutive frames
+#define R_BUFFERDATA_CYCLE 3
+
+typedef struct r_bufferdata_buffer_s
+{
+       struct r_bufferdata_buffer_s *purge; // older buffer to free on next frame
+       size_t size; // how much usable space
+       size_t current; // how much space in use
+       r_meshbuffer_t *buffer; // the buffer itself
+}
+r_bufferdata_buffer_t;
+
+static int r_bufferdata_cycle = 0; // incremented and wrapped each frame
+static r_bufferdata_buffer_t *r_bufferdata_buffer[R_BUFFERDATA_CYCLE][R_BUFFERDATA_COUNT];
+
+/// frees all dynamic buffers
+void R_BufferData_Reset(void)
+{
+       int cycle, type;
+       r_bufferdata_buffer_t **p, *mem;
+       for (cycle = 0;cycle < R_BUFFERDATA_CYCLE;cycle++)
+       {
+               for (type = 0;type < R_BUFFERDATA_COUNT;type++)
+               {
+                       // free all buffers
+                       p = &r_bufferdata_buffer[cycle][type];
+                       while (*p)
+                       {
+                               mem = *p;
+                               *p = (*p)->purge;
+                               if (mem->buffer)
+                                       R_Mesh_DestroyMeshBuffer(mem->buffer);
+                               Mem_Free(mem);
+                       }
+               }
+       }
+}
+
+// resize buffer as needed (this actually makes a new one, the old one will be recycled next frame)
+static void R_BufferData_Resize(r_bufferdata_type_t type, qboolean mustgrow, size_t minsize)
+{
+       r_bufferdata_buffer_t *mem = r_bufferdata_buffer[r_bufferdata_cycle][type];
+       size_t size;
+       float newvalue = r_buffermegs[type].value;
+
+       // increase the cvar if we have to (but only if we already have a mem)
+       if (mustgrow && mem)
+               newvalue *= 2.0f;
+       newvalue = bound(0.25f, newvalue, 256.0f);
+       while (newvalue * 1024*1024 < minsize)
+               newvalue *= 2.0f;
+
+       // clamp the cvar to valid range
+       newvalue = bound(0.25f, newvalue, 256.0f);
+       if (r_buffermegs[type].value != newvalue)
+               Cvar_SetValueQuick(&r_buffermegs[type], newvalue);
+
+       // calculate size in bytes
+       size = (size_t)(newvalue * 1024*1024);
+       size = bound(131072, size, 256*1024*1024);
+
+       // allocate a new buffer if the size is different (purge old one later)
+       // or if we were told we must grow the buffer
+       if (!mem || mem->size != size || mustgrow)
+       {
+               mem = (r_bufferdata_buffer_t *)Mem_Alloc(r_main_mempool, sizeof(*mem));
+               mem->size = size;
+               mem->current = 0;
+               if (type == R_BUFFERDATA_VERTEX)
+                       mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbuffervertex", false, false, true, false);
+               else if (type == R_BUFFERDATA_INDEX16)
+                       mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex16", true, false, true, true);
+               else if (type == R_BUFFERDATA_INDEX32)
+                       mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex32", true, false, true, false);
+               else if (type == R_BUFFERDATA_UNIFORM)
+                       mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferuniform", false, true, true, false);
+               mem->purge = r_bufferdata_buffer[r_bufferdata_cycle][type];
+               r_bufferdata_buffer[r_bufferdata_cycle][type] = mem;
+       }
+}
+
+void R_BufferData_NewFrame(void)
+{
+       int type;
+       r_bufferdata_buffer_t **p, *mem;
+       // cycle to the next frame's buffers
+       r_bufferdata_cycle = (r_bufferdata_cycle + 1) % R_BUFFERDATA_CYCLE;
+       // if we ran out of space on the last time we used these buffers, free the old memory now
+       for (type = 0;type < R_BUFFERDATA_COUNT;type++)
+       {
+               if (r_bufferdata_buffer[r_bufferdata_cycle][type])
+               {
+                       R_BufferData_Resize((r_bufferdata_type_t)type, false, 131072);
+                       // free all but the head buffer, this is how we recycle obsolete
+                       // buffers after they are no longer in use
+                       p = &r_bufferdata_buffer[r_bufferdata_cycle][type]->purge;
+                       while (*p)
+                       {
+                               mem = *p;
+                               *p = (*p)->purge;
+                               if (mem->buffer)
+                                       R_Mesh_DestroyMeshBuffer(mem->buffer);
+                               Mem_Free(mem);
+                       }
+                       // reset the current offset
+                       r_bufferdata_buffer[r_bufferdata_cycle][type]->current = 0;
+               }
+       }
+}
+
+r_meshbuffer_t *R_BufferData_Store(size_t datasize, const void *data, r_bufferdata_type_t type, int *returnbufferoffset)
+{
+       r_bufferdata_buffer_t *mem;
+       int offset = 0;
+       int padsize;
+
+       *returnbufferoffset = 0;
+
+       // align size to a byte boundary appropriate for the buffer type, this
+       // makes all allocations have aligned start offsets
+       if (type == R_BUFFERDATA_UNIFORM)
+               padsize = (datasize + r_uniformbufferalignment - 1) & ~(r_uniformbufferalignment - 1);
+       else
+               padsize = (datasize + 15) & ~15;
+
+       // if we ran out of space in this buffer we must allocate a new one
+       if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size)
+               R_BufferData_Resize(type, true, padsize);
+
+       // if the resize did not give us enough memory, fail
+       if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size)
+               Sys_Error("R_BufferData_Store: failed to create a new buffer of sufficient size\n");
+
+       mem = r_bufferdata_buffer[r_bufferdata_cycle][type];
+       offset = mem->current;
+       mem->current += padsize;
+
+       // upload the data to the buffer at the chosen offset
+       if (offset == 0)
+               R_Mesh_UpdateMeshBuffer(mem->buffer, NULL, mem->size, false, 0);
+       R_Mesh_UpdateMeshBuffer(mem->buffer, data, datasize, true, offset);
+
+       // count the usage for stats
+       r_refdef.stats[r_stat_bufferdatacurrent_vertex + type] = max(r_refdef.stats[r_stat_bufferdatacurrent_vertex + type], (int)mem->current);
+       r_refdef.stats[r_stat_bufferdatasize_vertex + type] = max(r_refdef.stats[r_stat_bufferdatasize_vertex + type], (int)mem->size);
+
+       // return the buffer offset
+       *returnbufferoffset = offset;
+
+       return mem->buffer;
+}
+
+//==================================================================================
+
 // LordHavoc: animcache originally written by Echon, rewritten since then
 
 /**
@@ -4581,13 +4867,24 @@ void R_AnimCache_ClearCache(void)
        {
                ent = r_refdef.scene.entities[i];
                ent->animcache_vertex3f = NULL;
+               ent->animcache_vertex3f_vertexbuffer = NULL;
+               ent->animcache_vertex3f_bufferoffset = 0;
                ent->animcache_normal3f = NULL;
+               ent->animcache_normal3f_vertexbuffer = NULL;
+               ent->animcache_normal3f_bufferoffset = 0;
                ent->animcache_svector3f = NULL;
+               ent->animcache_svector3f_vertexbuffer = NULL;
+               ent->animcache_svector3f_bufferoffset = 0;
                ent->animcache_tvector3f = NULL;
+               ent->animcache_tvector3f_vertexbuffer = NULL;
+               ent->animcache_tvector3f_bufferoffset = 0;
                ent->animcache_vertexmesh = NULL;
-               ent->animcache_vertex3fbuffer = NULL;
-               ent->animcache_vertexmeshbuffer = NULL;
+               ent->animcache_vertexmesh_vertexbuffer = NULL;
+               ent->animcache_vertexmesh_bufferoffset = 0;
                ent->animcache_skeletaltransform3x4 = NULL;
+               ent->animcache_skeletaltransform3x4buffer = NULL;
+               ent->animcache_skeletaltransform3x4offset = 0;
+               ent->animcache_skeletaltransform3x4size = 0;
        }
 }
 
@@ -4601,10 +4898,13 @@ static void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numver
 
        if (!ent->animcache_vertexmesh && ent->animcache_normal3f)
                ent->animcache_vertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(sizeof(r_vertexmesh_t)*numvertices);
-       // TODO: upload vertex3f buffer?
+       // TODO: upload vertexbuffer?
        if (ent->animcache_vertexmesh)
        {
-               memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.vertexmesh, sizeof(r_vertexmesh_t)*numvertices);
+               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.data_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]));
                if (ent->animcache_svector3f)
@@ -4616,7 +4916,6 @@ static void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numver
                if (ent->animcache_normal3f)
                        for (i = 0;i < numvertices;i++)
                                memcpy(ent->animcache_vertexmesh[i].normal3f, ent->animcache_normal3f + 3*i, sizeof(float[3]));
-               // TODO: upload vertexmeshbuffer?
        }
 }
 
@@ -4625,154 +4924,64 @@ 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)
        {
-               int i;
-               int blends;
-               const skeleton_t *skeleton = ent->skeleton;
-               const frameblend_t *frameblend = ent->frameblend;
-               float *boneposerelative;
-               float m[12];
-               static float bonepose[256][12];
-               ent->animcache_skeletaltransform3x4 = R_FrameData_Alloc(sizeof(float[3][4]) * model->num_bones);
-               boneposerelative = ent->animcache_skeletaltransform3x4;
-               if (skeleton && !skeleton->relativetransforms)
-                       skeleton = NULL;
-               // resolve hierarchy and make relative transforms (deforms) which the shader wants
-               if (skeleton)
-               {
-                       for (i = 0;i < model->num_bones;i++)
-                       {
-                               Matrix4x4_ToArray12FloatD3D(&skeleton->relativetransforms[i], m);
-                               if (model->data_bones[i].parent >= 0)
-                                       R_ConcatTransforms(bonepose[model->data_bones[i].parent], m, bonepose[i]);
-                               else
-                                       memcpy(bonepose[i], m, sizeof(m));
-
-                               // create a relative deformation matrix to describe displacement
-                               // from the base mesh, which is used by the actual weighting
-                               R_ConcatTransforms(bonepose[i], model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12);
-                       }
-               }
-               else
-               {
-                       for (i = 0;i < model->num_bones;i++)
-                       {
-                               const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[0].subframe * model->num_bones + i);
-                               float lerp = frameblend[0].lerp,
-                                       tx = pose7s[0], ty = pose7s[1], tz = pose7s[2],
-                                       rx = pose7s[3] * lerp,
-                                       ry = pose7s[4] * lerp,
-                                       rz = pose7s[5] * lerp,
-                                       rw = pose7s[6] * lerp,
-                                       dx = tx*rw + ty*rz - tz*ry,
-                                       dy = -tx*rz + ty*rw + tz*rx,
-                                       dz = tx*ry - ty*rx + tz*rw,
-                                       dw = -tx*rx - ty*ry - tz*rz,
-                                       scale, sx, sy, sz, sw;
-                               for (blends = 1;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++)
-                               {
-                                       const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[blends].subframe * model->num_bones + i);
-                                       float lerp = frameblend[blends].lerp,
-                                               tx = pose7s[0], ty = pose7s[1], tz = pose7s[2],
-                                               qx = pose7s[3], qy = pose7s[4], qz = pose7s[5], qw = pose7s[6];
-                                       if(rx*qx + ry*qy + rz*qz + rw*qw < 0) lerp = -lerp;
-                                       qx *= lerp;
-                                       qy *= lerp;
-                                       qz *= lerp;
-                                       qw *= lerp;
-                                       rx += qx;
-                                       ry += qy;
-                                       rz += qz;
-                                       rw += qw;
-                                       dx += tx*qw + ty*qz - tz*qy;
-                                       dy += -tx*qz + ty*qw + tz*qx;
-                                       dz += tx*qy - ty*qx + tz*qw;
-                                       dw += -tx*qx - ty*qy - tz*qz;
-                               }
-                               scale = 1.0f / (rx*rx + ry*ry + rz*rz + rw*rw);
-                               sx = rx * scale;
-                               sy = ry * scale;
-                               sz = rz * scale;
-                               sw = rw * scale;
-                               m[0] = sw*rw + sx*rx - sy*ry - sz*rz;
-                               m[1] = 2*(sx*ry - sw*rz);
-                               m[2] = 2*(sx*rz + sw*ry);
-                               m[3] = model->num_posescale*(dx*sw - dy*sz + dz*sy - dw*sx);
-                               m[4] = 2*(sx*ry + sw*rz);
-                               m[5] = sw*rw + sy*ry - sx*rx - sz*rz;
-                               m[6] = 2*(sy*rz - sw*rx);
-                               m[7] = model->num_posescale*(dx*sz + dy*sw - dz*sx - dw*sy);
-                               m[8] = 2*(sx*rz - sw*ry);
-                               m[9] = 2*(sy*rz + sw*rx);
-                               m[10] = sw*rw + sz*rz - sx*rx - sy*ry;
-                               m[11] = model->num_posescale*(dy*sx + dz*sw - dx*sy - dw*sz);
-                               if (i == r_skeletal_debugbone.integer)
-                                       m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value;
-                               m[3] *= r_skeletal_debugtranslatex.value;
-                               m[7] *= r_skeletal_debugtranslatey.value;
-                               m[11] *= r_skeletal_debugtranslatez.value;
-                               if (model->data_bones[i].parent >= 0)
-                                       R_ConcatTransforms(bonepose[model->data_bones[i].parent], m, bonepose[i]);
-                               else
-                                       memcpy(bonepose[i], m, sizeof(m));
-                               // create a relative deformation matrix to describe displacement
-                               // from the base mesh, which is used by the actual weighting
-                               R_ConcatTransforms(bonepose[i], model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12);
-                       }
-               }
+               // 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;
        }
 
-       // see if it's already cached this frame
-       if (ent->animcache_vertex3f)
+       // check which kind of cache we need to generate
+       if (r_gpuskeletal && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub)
        {
-               // add normals/tangents if needed (this only happens with multiple views, reflections, cameras, etc)
+               // cache the skeleton so the vertex shader can use it
+               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);
+               Mod_Skeletal_BuildTransforms(model, ent->frameblend, ent->skeleton, NULL, ent->animcache_skeletaltransform3x4); 
+               // note: this can fail if the buffer is at the grow limit
+               ent->animcache_skeletaltransform3x4size = sizeof(float[3][4]) * model->num_bones;
+               ent->animcache_skeletaltransform3x4buffer = R_BufferData_Store(ent->animcache_skeletaltransform3x4size, ent->animcache_skeletaltransform3x4, R_BUFFERDATA_UNIFORM, &ent->animcache_skeletaltransform3x4offset);
+       }
+       else if (ent->animcache_vertex3f)
+       {
+               // 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 && !r_showsurfaces.integer) // FIXME add r_showsurfaces support to GLSL skeletal!
-               {
-                       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)
@@ -4784,6 +4993,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;
 }
@@ -5070,7 +5288,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))
                {
@@ -5480,7 +5698,9 @@ void R_EntityMatrix(const matrix4x4_t *matrix)
                case RENDERPATH_GL11:
                case RENDERPATH_GL13:
                case RENDERPATH_GLES1:
+#ifndef USE_GLES2
                        qglLoadMatrixf(gl_modelview16f);CHECKGLERROR
+#endif
                        break;
                case RENDERPATH_SOFT:
                        DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f);
@@ -5642,10 +5862,10 @@ static void R_Water_StartFrame(void)
        }
        else
        {
-               for (texturewidth   = 1;texturewidth   < waterwidth ;texturewidth   *= 2);
-               for (textureheight  = 1;textureheight  < waterheight;textureheight  *= 2);
-               for (camerawidth    = 1;camerawidth   <= waterwidth; camerawidth    *= 2); camerawidth  /= 2;
-               for (cameraheight   = 1;cameraheight  <= waterheight;cameraheight   *= 2); cameraheight /= 2;
+               for (texturewidth   = 1;texturewidth     <  waterwidth ;texturewidth   *= 2);
+               for (textureheight  = 1;textureheight    <  waterheight;textureheight  *= 2);
+               for (camerawidth    = 1;camerawidth  * 2 <= waterwidth ;camerawidth    *= 2);
+               for (cameraheight   = 1;cameraheight * 2 <= waterheight;cameraheight   *= 2);
        }
 
        // allocate textures as needed
@@ -6331,14 +6551,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
 
@@ -6370,14 +6590,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
@@ -6403,13 +6623,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;
                }
        }
 
@@ -6455,7 +6675,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);
                }
 
@@ -6463,7 +6683,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;
                }
        }
 }
@@ -6495,7 +6715,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)
@@ -6561,7 +6781,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
@@ -6569,7 +6789,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;
                        }
                }
@@ -6677,7 +6897,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:
@@ -6825,9 +7045,11 @@ void R_UpdateVariables(void)
                r_refdef.lightmapintensity = 0;
        }
 
+       r_gpuskeletal = false;
        switch(vid.renderpath)
        {
        case RENDERPATH_GL20:
+               r_gpuskeletal = vid.support.arb_uniform_buffer_object && r_glsl_skeletal.integer && !r_showsurfaces.integer; // FIXME add r_showsurfaces support to GLSL skeletal!
        case RENDERPATH_D3D9:
        case RENDERPATH_D3D10:
        case RENDERPATH_D3D11:
@@ -6968,7 +7190,6 @@ void R_RenderView(void)
                R_SortEntities();
 
        R_AnimCache_ClearCache();
-       R_FrameData_NewFrame();
 
        /* adjust for stereo display */
        if(R_Stereo_Active())
@@ -7112,7 +7333,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();
 
@@ -8216,6 +8437,9 @@ void RSurf_ActiveWorldEntity(void)
        rsurface.basepolygonfactor = r_refdef.polygonfactor;
        rsurface.basepolygonoffset = r_refdef.polygonoffset;
        rsurface.entityskeletaltransform3x4 = NULL;
+       rsurface.entityskeletaltransform3x4buffer = NULL;
+       rsurface.entityskeletaltransform3x4offset = 0;
+       rsurface.entityskeletaltransform3x4size = 0;;
        rsurface.entityskeletalnumtransforms = 0;
        rsurface.modelvertex3f  = model->surfmesh.data_vertex3f;
        rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
@@ -8254,9 +8478,9 @@ void RSurf_ActiveWorldEntity(void)
        rsurface.modelnumvertices = model->surfmesh.num_vertices;
        rsurface.modelnumtriangles = model->surfmesh.num_triangles;
        rsurface.modelsurfaces = model->data_surfaces;
-       rsurface.modelvertexmesh = model->surfmesh.vertexmesh;
-       rsurface.modelvertexmeshbuffer = model->surfmesh.vertexmeshbuffer;
-       rsurface.modelvertex3fbuffer = model->surfmesh.vertex3fbuffer;
+       rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh;
+       rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+       rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f;
        rsurface.modelgeneratedvertex = false;
        rsurface.batchgeneratedvertex = false;
        rsurface.batchfirstvertex = 0;
@@ -8291,8 +8515,8 @@ void RSurf_ActiveWorldEntity(void)
        rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
        rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
-       rsurface.batchvertexmeshbuffer = NULL;
-       rsurface.batchvertex3fbuffer = NULL;
+       rsurface.batchvertexmesh_vertexbuffer = NULL;
+       rsurface.batchvertexmesh_bufferoffset = 0;
        rsurface.batchelement3i = NULL;
        rsurface.batchelement3i_indexbuffer = NULL;
        rsurface.batchelement3i_bufferoffset = 0;
@@ -8346,65 +8570,128 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                rsurface.basepolygonoffset += r_polygonoffset_submodel_offset.value;
        }
        // if the animcache code decided it should use the shader path, skip the deform step
-       rsurface.entityskeletaltransform3x4 = ent->animcache_vertex3f ? NULL : ent->animcache_skeletaltransform3x4;
+       rsurface.entityskeletaltransform3x4 = ent->animcache_skeletaltransform3x4;
+       rsurface.entityskeletaltransform3x4buffer = ent->animcache_skeletaltransform3x4buffer;
+       rsurface.entityskeletaltransform3x4offset = ent->animcache_skeletaltransform3x4offset;
+       rsurface.entityskeletaltransform3x4size = ent->animcache_skeletaltransform3x4size;
        rsurface.entityskeletalnumtransforms = rsurface.entityskeletaltransform3x4 ? model->num_bones : 0;
        if (model->surfmesh.isanimated && model->AnimateVertices && !rsurface.entityskeletaltransform3x4)
        {
                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.modelvertex3f_vertexbuffer = ent->animcache_vertex3f_vertexbuffer;
+                       rsurface.modelvertex3f_bufferoffset = ent->animcache_vertex3f_bufferoffset;
                        rsurface.modelsvector3f = wanttangents ? ent->animcache_svector3f : NULL;
+                       rsurface.modelsvector3f_vertexbuffer = wanttangents ? ent->animcache_svector3f_vertexbuffer : NULL;
+                       rsurface.modelsvector3f_bufferoffset = wanttangents ? ent->animcache_svector3f_bufferoffset : 0;
                        rsurface.modeltvector3f = wanttangents ? ent->animcache_tvector3f : NULL;
+                       rsurface.modeltvector3f_vertexbuffer = wanttangents ? ent->animcache_tvector3f_vertexbuffer : NULL;
+                       rsurface.modeltvector3f_bufferoffset = wanttangents ? ent->animcache_tvector3f_bufferoffset : 0;
                        rsurface.modelnormal3f = wantnormals ? ent->animcache_normal3f : NULL;
+                       rsurface.modelnormal3f_vertexbuffer = wantnormals ? ent->animcache_normal3f_vertexbuffer : NULL;
+                       rsurface.modelnormal3f_bufferoffset = wantnormals ? ent->animcache_normal3f_bufferoffset : 0;
                        rsurface.modelvertexmesh = ent->animcache_vertexmesh;
-                       rsurface.modelvertexmeshbuffer = ent->animcache_vertexmeshbuffer;
-                       rsurface.modelvertex3fbuffer = ent->animcache_vertex3fbuffer;
+                       rsurface.modelvertexmesh_vertexbuffer = ent->animcache_vertexmesh_vertexbuffer;
+                       rsurface.modelvertexmesh_bufferoffset = ent->animcache_vertexmesh_bufferoffset;
                }
                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]));
                        rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, rsurface.modelsvector3f, rsurface.modeltvector3f);
                        rsurface.modelvertexmesh = NULL;
-                       rsurface.modelvertexmeshbuffer = NULL;
-                       rsurface.modelvertex3fbuffer = NULL;
+                       rsurface.modelvertexmesh_vertexbuffer = NULL;
+                       rsurface.modelvertexmesh_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = NULL;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = 0;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelsvector3f_vertexbuffer = 0;
+                       rsurface.modelsvector3f_bufferoffset = 0;
+                       rsurface.modeltvector3f_vertexbuffer = 0;
+                       rsurface.modeltvector3f_bufferoffset = 0;
+                       rsurface.modelnormal3f_vertexbuffer = 0;
+                       rsurface.modelnormal3f_bufferoffset = 0;
                }
                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;
                        rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3]));
                        model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, NULL, NULL);
                        rsurface.modelvertexmesh = NULL;
-                       rsurface.modelvertexmeshbuffer = NULL;
-                       rsurface.modelvertex3fbuffer = NULL;
+                       rsurface.modelvertexmesh_vertexbuffer = NULL;
+                       rsurface.modelvertexmesh_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = NULL;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = 0;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelsvector3f_vertexbuffer = 0;
+                       rsurface.modelsvector3f_bufferoffset = 0;
+                       rsurface.modeltvector3f_vertexbuffer = 0;
+                       rsurface.modeltvector3f_bufferoffset = 0;
+                       rsurface.modelnormal3f_vertexbuffer = 0;
+                       rsurface.modelnormal3f_bufferoffset = 0;
                }
                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;
                        rsurface.modelnormal3f = NULL;
                        model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, NULL, NULL, NULL);
                        rsurface.modelvertexmesh = NULL;
-                       rsurface.modelvertexmeshbuffer = NULL;
-                       rsurface.modelvertex3fbuffer = NULL;
-               }
-               rsurface.modelvertex3f_vertexbuffer = 0;
-               rsurface.modelvertex3f_bufferoffset = 0;
-               rsurface.modelsvector3f_vertexbuffer = 0;
-               rsurface.modelsvector3f_bufferoffset = 0;
-               rsurface.modeltvector3f_vertexbuffer = 0;
-               rsurface.modeltvector3f_bufferoffset = 0;
-               rsurface.modelnormal3f_vertexbuffer = 0;
-               rsurface.modelnormal3f_bufferoffset = 0;
+                       rsurface.modelvertexmesh_vertexbuffer = NULL;
+                       rsurface.modelvertexmesh_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = NULL;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelvertex3f_vertexbuffer = 0;
+                       rsurface.modelvertex3f_bufferoffset = 0;
+                       rsurface.modelsvector3f_vertexbuffer = 0;
+                       rsurface.modelsvector3f_bufferoffset = 0;
+                       rsurface.modeltvector3f_vertexbuffer = 0;
+                       rsurface.modeltvector3f_bufferoffset = 0;
+                       rsurface.modelnormal3f_vertexbuffer = 0;
+                       rsurface.modelnormal3f_bufferoffset = 0;
+               }
                rsurface.modelgeneratedvertex = true;
        }
        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;
@@ -8417,9 +8704,9 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                rsurface.modelnormal3f  = model->surfmesh.data_normal3f;
                rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
                rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f;
-               rsurface.modelvertexmesh = model->surfmesh.vertexmesh;
-               rsurface.modelvertexmeshbuffer = model->surfmesh.vertexmeshbuffer;
-               rsurface.modelvertex3fbuffer = model->surfmesh.vertex3fbuffer;
+               rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh;
+               rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+               rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f;
                rsurface.modelgeneratedvertex = false;
        }
        rsurface.modellightmapcolor4f  = model->surfmesh.data_lightmapcolor4f;
@@ -8480,8 +8767,8 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
        rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
-       rsurface.batchvertexmeshbuffer = NULL;
-       rsurface.batchvertex3fbuffer = NULL;
+       rsurface.batchvertexmesh_vertexbuffer = NULL;
+       rsurface.batchvertexmesh_bufferoffset = 0;
        rsurface.batchelement3i = NULL;
        rsurface.batchelement3i_indexbuffer = NULL;
        rsurface.batchelement3i_bufferoffset = 0;
@@ -8528,7 +8815,14 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
        rsurface.basepolygonfactor = r_refdef.polygonfactor;
        rsurface.basepolygonoffset = r_refdef.polygonoffset;
        rsurface.entityskeletaltransform3x4 = NULL;
+       rsurface.entityskeletaltransform3x4buffer = NULL;
+       rsurface.entityskeletaltransform3x4offset = 0;
+       rsurface.entityskeletaltransform3x4size = 0;
        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;
@@ -8551,8 +8845,8 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
                rsurface.modelnormal3f = NULL;
        }
        rsurface.modelvertexmesh = NULL;
-       rsurface.modelvertexmeshbuffer = NULL;
-       rsurface.modelvertex3fbuffer = NULL;
+       rsurface.modelvertexmesh_vertexbuffer = NULL;
+       rsurface.modelvertexmesh_bufferoffset = 0;
        rsurface.modelvertex3f_vertexbuffer = 0;
        rsurface.modelvertex3f_bufferoffset = 0;
        rsurface.modelsvector3f_vertexbuffer = 0;
@@ -8618,8 +8912,8 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
        rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
        rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
-       rsurface.batchvertexmeshbuffer = NULL;
-       rsurface.batchvertex3fbuffer = NULL;
+       rsurface.batchvertexmesh_vertexbuffer = NULL;
+       rsurface.batchvertexmesh_bufferoffset = 0;
        rsurface.batchelement3i = NULL;
        rsurface.batchelement3i_indexbuffer = NULL;
        rsurface.batchelement3i_bufferoffset = 0;
@@ -8701,6 +8995,7 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        int surfacefirstvertex;
        int surfaceendvertex;
        int surfacenumvertices;
+       int batchnumsurfaces = texturenumsurfaces;
        int batchnumvertices;
        int batchnumtriangles;
        int needsupdate;
@@ -8742,6 +9037,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;
@@ -8757,11 +9059,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;
        }
@@ -8783,16 +9101,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;
@@ -8800,11 +9139,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;
@@ -8812,6 +9165,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;
@@ -8824,16 +9184,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;
@@ -8841,6 +9222,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;
@@ -8848,20 +9236,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.modelvertexmesh_vertexbuffer)
                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)
        {
@@ -8877,11 +9281,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)
@@ -8914,9 +9336,9 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        rsurface.batchskeletalweight4ub = rsurface.modelskeletalweight4ub;
        rsurface.batchskeletalweight4ub_vertexbuffer = rsurface.modelskeletalweight4ub_vertexbuffer;
        rsurface.batchskeletalweight4ub_bufferoffset = rsurface.modelskeletalweight4ub_bufferoffset;
-       rsurface.batchvertex3fbuffer = rsurface.modelvertex3fbuffer;
        rsurface.batchvertexmesh = rsurface.modelvertexmesh;
-       rsurface.batchvertexmeshbuffer = rsurface.modelvertexmeshbuffer;
+       rsurface.batchvertexmesh_vertexbuffer = rsurface.modelvertexmesh_vertexbuffer;
+       rsurface.batchvertexmesh_bufferoffset = rsurface.modelvertexmesh_bufferoffset;
        rsurface.batchelement3i = rsurface.modelelement3i;
        rsurface.batchelement3i_indexbuffer = rsurface.modelelement3i_indexbuffer;
        rsurface.batchelement3i_bufferoffset = rsurface.modelelement3i_bufferoffset;
@@ -8924,6 +9346,9 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        rsurface.batchelement3s_indexbuffer = rsurface.modelelement3s_indexbuffer;
        rsurface.batchelement3s_bufferoffset = rsurface.modelelement3s_bufferoffset;
        rsurface.batchskeletaltransform3x4 = rsurface.entityskeletaltransform3x4;
+       rsurface.batchskeletaltransform3x4buffer = rsurface.entityskeletaltransform3x4buffer;
+       rsurface.batchskeletaltransform3x4offset = rsurface.entityskeletaltransform3x4offset;
+       rsurface.batchskeletaltransform3x4size = rsurface.entityskeletaltransform3x4size;
        rsurface.batchskeletalnumtransforms = rsurface.entityskeletalnumtransforms;
 
        // if any dynamic vertex processing has to occur in software, we copy the
@@ -8952,6 +9377,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;
@@ -8982,6 +9411,21 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                                for (i = 0;i < numtriangles*3;i++)
                                        rsurface.batchelement3s[i] = rsurface.batchelement3i[i];
                        }
+                       // upload buffer data for the copytriangles batch
+                       if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo)
+                       {
+                               if (rsurface.batchelement3s)
+                                       rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset);
+                               else if (rsurface.batchelement3i)
+                                       rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset);
+                       }
+               }
+               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;
        }
@@ -8990,6 +9434,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)
@@ -8998,9 +9446,9 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        // need actual vertex positions and normals
        //if (dynamicvertex)
        {
-               rsurface.batchvertex3fbuffer = NULL;
                rsurface.batchvertexmesh = NULL;
-               rsurface.batchvertexmeshbuffer = NULL;
+               rsurface.batchvertexmesh_vertexbuffer = NULL;
+               rsurface.batchvertexmesh_bufferoffset = 0;
                rsurface.batchvertex3f = NULL;
                rsurface.batchvertex3f_vertexbuffer = NULL;
                rsurface.batchvertex3f_bufferoffset = 0;
@@ -9034,6 +9482,9 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                rsurface.batchelement3s = NULL;
                rsurface.batchelement3s_indexbuffer = NULL;
                rsurface.batchelement3s_bufferoffset = 0;
+               rsurface.batchskeletaltransform3x4buffer = NULL;
+               rsurface.batchskeletaltransform3x4offset = 0;
+               rsurface.batchskeletaltransform3x4size = 0;
                // we'll only be setting up certain arrays as needed
                if (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))
                        rsurface.batchvertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t));
@@ -9167,6 +9618,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;
@@ -9696,7 +10151,8 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        {
                // convert the modified arrays to vertex structs
 //             rsurface.batchvertexmesh = R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t));
-//             rsurface.batchvertexmeshbuffer = NULL;
+//             rsurface.batchvertexmesh_vertexbuffer = NULL;
+//             rsurface.batchvertexmesh_bufferoffset = 0;
                if (batchneed & BATCHNEED_VERTEXMESH_VERTEX)
                        for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++)
                                VectorCopy(rsurface.batchvertex3f + 3*j, vertexmesh->vertex3f);
@@ -9729,6 +10185,38 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                        }
                }
        }
+
+       // upload buffer data for the dynamic batch
+       if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicvertex.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo)
+       {
+               if (rsurface.batchvertexmesh)
+                       rsurface.batchvertexmesh_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(r_vertexmesh_t), rsurface.batchvertexmesh, R_BUFFERDATA_VERTEX, &rsurface.batchvertexmesh_bufferoffset);
+               else
+               {
+                       if (rsurface.batchvertex3f)
+                               rsurface.batchvertex3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f, R_BUFFERDATA_VERTEX, &rsurface.batchvertex3f_bufferoffset);
+                       if (rsurface.batchsvector3f)
+                               rsurface.batchsvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchsvector3f_bufferoffset);
+                       if (rsurface.batchtvector3f)
+                               rsurface.batchtvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchtvector3f_bufferoffset);
+                       if (rsurface.batchnormal3f)
+                               rsurface.batchnormal3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f, R_BUFFERDATA_VERTEX, &rsurface.batchnormal3f_bufferoffset);
+                       if (rsurface.batchlightmapcolor4f)
+                               rsurface.batchlightmapcolor4f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[4]), rsurface.batchlightmapcolor4f, R_BUFFERDATA_VERTEX, &rsurface.batchlightmapcolor4f_bufferoffset);
+                       if (rsurface.batchtexcoordtexture2f)
+                               rsurface.batchtexcoordtexture2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordtexture2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordtexture2f_bufferoffset);
+                       if (rsurface.batchtexcoordlightmap2f)
+                               rsurface.batchtexcoordlightmap2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordlightmap2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordlightmap2f_bufferoffset);
+                       if (rsurface.batchskeletalindex4ub)
+                               rsurface.batchskeletalindex4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalindex4ub_bufferoffset);
+                       if (rsurface.batchskeletalweight4ub)
+                               rsurface.batchskeletalweight4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalweight4ub_bufferoffset);
+               }
+               if (rsurface.batchelement3s)
+                       rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset);
+               else if (rsurface.batchelement3i)
+                       rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset);
+       }
 }
 
 void RSurf_DrawBatch(void)
@@ -9953,6 +10441,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();
 }
 
@@ -10155,10 +10645,7 @@ static void R_DrawTextureSurfaceList_Sky(int texturenumsurfaces, const msurface_
                        // anything despite that colormask...
                        GL_BlendFunc(GL_ZERO, GL_ONE);
                        RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
-                       if (rsurface.batchvertex3fbuffer)
-                               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
-                       else
-                               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer);
+                       R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset);
                }
                else
                {
@@ -10772,10 +11259,7 @@ static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const
                        RSurf_SetupDepthAndCulling();
                        RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
                        R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4);
-                       if (rsurface.batchvertex3fbuffer)
-                               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
-                       else
-                               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer);
+                       R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset);
                        RSurf_DrawBatch();
                }
                if (setup)
@@ -10867,10 +11351,7 @@ static void R_DrawTextureSurfaceList_DepthOnly(int texturenumsurfaces, const msu
                return;
        RSurf_SetupDepthAndCulling();
        RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
-       if (rsurface.batchvertex3fbuffer)
-               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
-       else
-               R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer);
+       R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset);
        R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4);
        RSurf_DrawBatch();
 }
@@ -11702,7 +12183,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!)
@@ -11740,7 +12221,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;
@@ -11760,10 +12241,9 @@ extern cvar_t mod_collision_bih;
 static void R_DrawDebugModel(void)
 {
        entity_render_t *ent = rsurface.entity;
-       int i, j, k, l, flagsmask;
+       int i, j, flagsmask;
        const msurface_t *surface;
        dp_model_t *model = ent->model;
-       vec3_t v;
 
        if (!sv.active  && !cls.demoplayback && ent != r_refdef.scene.worldentity)
                return;
@@ -11895,6 +12375,8 @@ static void R_DrawDebugModel(void)
 
        if (r_shownormals.value != 0 && qglBegin)
        {
+               int l, k;
+               vec3_t v;
                if (r_showdisabledepthtest.integer)
                {
                        GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -12060,9 +12542,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
@@ -12196,9 +12678,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