]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - gl_rmain.c
fix C++ compile error
[xonotic/darkplaces.git] / gl_rmain.c
index 3b44beeb927026b874c3e019a881d3ea1493875a..20f94ecd73d76cebdc77e350ee3d5ad36588b08a 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_bufferdatasize[R_BUFFERDATA_COUNT] =
+{
+       {CVAR_SAVE, "r_bufferdatasize_vertex", "4", "vertex buffer size for one frame"},
+       {CVAR_SAVE, "r_bufferdatasize_index16", "1", "index buffer size for one frame (16bit indices)"},
+       {CVAR_SAVE, "r_bufferdatasize_index32", "1", "index buffer size for one frame (32bit indices)"},
+       {CVAR_SAVE, "r_bufferdatasize_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;
 
@@ -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";
@@ -1205,7 +1231,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 +1279,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 +1310,14 @@ 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
+               p->ubiloc_Skeletal_Transform12_UniformBlock = qglGetUniformBlockIndex(p->program, "Skeletal_Transform12_UniformBlock");
+               // clear the uniform block bindings
+               p->ubibind_Skeletal_Transform12_UniformBlock = -1;
+               // bind the uniform blocks in use
+               ubibind = 0;
+               if (p->ubiloc_Skeletal_Transform12_UniformBlock >= 0) {p->ubibind_Skeletal_Transform12_UniformBlock = ubibind;qglUniformBlockBinding(p->program, p->ubiloc_Skeletal_Transform12_UniformBlock, ubibind);ubibind++;}
+               // 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);
        }
@@ -1965,8 +1999,10 @@ void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemod
        case RENDERPATH_GL20:
        case RENDERPATH_GLES2:
                R_SetupShader_SetPermutationGLSL(SHADERMODE_GENERIC, permutation);
-               R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first );
-               R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second);
+               if (r_glsl_permutation->tex_Texture_First >= 0)
+                       R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first );
+               if (r_glsl_permutation->tex_Texture_Second >= 0)
+                       R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second);
                if (r_glsl_permutation->tex_Texture_GammaRamps >= 0)
                        R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps);
                break;
@@ -1974,12 +2010,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);
@@ -2022,6 +2064,7 @@ void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean
        case RENDERPATH_GL20:
        case RENDERPATH_GLES2:
                R_SetupShader_SetPermutationGLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation);
+               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);
                break;
        case RENDERPATH_GL13:
        case RENDERPATH_GLES1:
@@ -2506,7 +2549,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)
@@ -2673,12 +2716,13 @@ 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);
+               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);
                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)
                {
@@ -2819,8 +2863,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:
@@ -3943,6 +3985,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;
@@ -3961,6 +4004,8 @@ static void gl_main_start(void)
                r_loadnormalmap = true;
                r_loadgloss = true;
                r_loadfog = false;
+               if (vid.support.arb_uniform_buffer_object)
+                       qglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &r_uniformbufferalignment);
                break;
        case RENDERPATH_GL13:
        case RENDERPATH_GLES1:
@@ -3983,6 +4028,7 @@ static void gl_main_start(void)
 
        R_AnimCache_Free();
        R_FrameData_Reset();
+       R_BufferData_Reset();
 
        r_numqueries = 0;
        r_maxqueries = 0;
@@ -4034,6 +4080,7 @@ static void gl_main_shutdown(void)
 {
        R_AnimCache_Free();
        R_FrameData_Reset();
+       R_BufferData_Reset();
 
        R_Main_FreeViewCache();
 
@@ -4127,10 +4174,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");
@@ -4303,6 +4352,9 @@ 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_bufferdatasize[i]);
+       Cvar_RegisterVariable(&r_batch_dynamicbuffer);
        if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE)
                Cvar_SetValue("r_fullbrights", 0);
        R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL);
@@ -4488,12 +4540,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;
@@ -4508,7 +4560,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
@@ -4527,6 +4579,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
@@ -4535,8 +4588,10 @@ 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;
@@ -4573,6 +4628,146 @@ void R_FrameData_ReturnToMark(void)
 
 //==================================================================================
 
+// avoid reusing the same buffer objects on consecutive buffers
+#define R_BUFFERDATA_CYCLE 2
+
+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[r_bufferdata_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)
+{
+       r_bufferdata_buffer_t *mem = r_bufferdata_buffer[r_bufferdata_cycle][type];
+       size_t size;
+       size = (size_t)(r_bufferdatasize[type].value * 1024*1024);
+       size = bound(65536, size, 512*1024*1024);
+       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);
+                       // 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, void *data, r_bufferdata_type_t type, int *returnbufferoffset, qboolean allowfail)
+{
+       r_bufferdata_buffer_t *mem;
+       int offset = 0;
+       int padsize;
+       float newvalue;
+
+       *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;
+
+       while (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size)
+       {
+               // emergency - we ran out of space, allocate more memory
+               newvalue = bound(0.25f, r_bufferdatasize[type].value * 2.0f, 256.0f);
+               // if we're already at the limit, just fail (if allowfail is false we might run out of video ram)
+               if (newvalue == r_bufferdatasize[type].value && allowfail)
+                       return NULL;
+               Cvar_SetValueQuick(&r_bufferdatasize[type], newvalue);
+               R_BufferData_Resize(type, true);
+       }
+
+       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
 
 /**
@@ -4593,13 +4788,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;
        }
 }
 
@@ -4613,13 +4819,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)
        {
                r_refdef.stats[r_stat_animcache_vertexmesh_count] += 1;
                r_refdef.stats[r_stat_animcache_vertexmesh_vertices] += numvertices;
                r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices] = max(r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices], numvertices);
-               memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.vertexmesh, sizeof(r_vertexmesh_t)*numvertices);
+               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)
@@ -4631,7 +4837,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?
        }
 }
 
@@ -4640,9 +4845,31 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
        dp_model_t *model = ent->model;
        int numvertices;
 
-       // cache skeletal animation data first (primarily for gpu-skinning)
-       if (!ent->animcache_skeletaltransform3x4 && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub)
+       // see if this ent is worth caching
+       if (!model || !model->Draw || !model->AnimateVertices)
+               return false;
+       // nothing to cache if it contains no animations and has no skeleton
+       if (!model->surfmesh.isanimated && !(model->num_bones && ent->skeleton && ent->skeleton->relativetransforms))
+               return false;
+       // see if it is already cached for gpuskeletal
+       if (ent->animcache_skeletaltransform3x4)
+               return false;
+       // see if it is already cached as a mesh
+       if (ent->animcache_vertex3f)
        {
+               // check if we need to add normals or tangents
+               if (ent->animcache_normal3f)
+                       wantnormals = false;
+               if (ent->animcache_svector3f)
+                       wanttangents = false;
+               if (!wantnormals && !wanttangents)
+                       return false;
+       }
+
+       // check which kind of cache we need to generate
+       if (r_gpuskeletal && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub)
+       {
+               // cache the skeleton so the vertex shader can use it
                int i;
                int blends;
                const skeleton_t *skeleton = ent->skeleton;
@@ -4740,60 +4967,34 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
                                R_ConcatTransforms(bonepose[i], model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12);
                        }
                }
+               // 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, true);
        }
-
-       // see if it's already cached this frame
-       if (ent->animcache_vertex3f)
+       else if (ent->animcache_vertex3f)
        {
-               // add normals/tangents if needed (this only happens with multiple views, reflections, cameras, etc)
+               // mesh was already cached but we may need to add normals/tangents
+               // (this only happens with multiple views, reflections, cameras, etc)
                if (wantnormals || wanttangents)
                {
-                       if (ent->animcache_normal3f)
-                               wantnormals = false;
-                       if (ent->animcache_svector3f)
-                               wanttangents = false;
-                       if (wantnormals || wanttangents)
+                       numvertices = model->surfmesh.num_vertices;
+                       if (wantnormals)
+                               ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
+                       if (wanttangents)
                        {
-                               numvertices = model->surfmesh.num_vertices;
-                               if (wantnormals)
-                                       ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                               if (wanttangents)
-                               {
-                                       ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                                       ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
-                               }
-                               model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL);
-                               R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices);
-                               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);
+                               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)
@@ -6855,9 +7056,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:
@@ -6999,6 +7202,7 @@ void R_RenderView(void)
 
        R_AnimCache_ClearCache();
        R_FrameData_NewFrame();
+       R_BufferData_NewFrame();
 
        /* adjust for stereo display */
        if(R_Stereo_Active())
@@ -8246,6 +8450,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;
@@ -8284,9 +8491,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;
@@ -8321,8 +8528,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;
@@ -8376,65 +8583,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;
@@ -8447,9 +8717,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;
@@ -8510,8 +8780,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;
@@ -8558,7 +8828,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;
@@ -8581,8 +8858,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;
@@ -8648,8 +8925,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;
@@ -8985,7 +9262,7 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
 
        // 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
@@ -9072,9 +9349,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;
@@ -9082,6 +9359,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
@@ -9144,6 +9424,14 @@ 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 (vid.forcevbo || (r_batch_dynamicbuffer.integer && vid.support.arb_vertex_buffer_object))
+                       {
+                               if (rsurface.batchelement3s)
+                                       rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset, !vid.forcevbo);
+                               else if (rsurface.batchelement3i)
+                                       rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset, !vid.forcevbo);
+                       }
                }
                else
                {
@@ -9171,9 +9459,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;
@@ -9207,6 +9495,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));
@@ -9873,7 +10164,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);
@@ -9906,6 +10198,38 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                        }
                }
        }
+
+       // upload buffer data for the dynamic batch
+       if (vid.forcevbo || (r_batch_dynamicbuffer.integer && vid.support.arb_vertex_buffer_object))
+       {
+               if (rsurface.batchvertexmesh)
+                       rsurface.batchvertexmesh_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(r_vertexmesh_t), rsurface.batchvertexmesh, R_BUFFERDATA_VERTEX, &rsurface.batchvertexmesh_bufferoffset, !vid.forcevbo);
+               else
+               {
+                       if (rsurface.batchvertex3f)
+                               rsurface.batchvertex3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f, R_BUFFERDATA_VERTEX, &rsurface.batchvertex3f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchsvector3f)
+                               rsurface.batchsvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchsvector3f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchtvector3f)
+                               rsurface.batchtvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchtvector3f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchnormal3f)
+                               rsurface.batchnormal3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f, R_BUFFERDATA_VERTEX, &rsurface.batchnormal3f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchlightmapcolor4f && r_batch_dynamicbuffer.integer && vid.support.arb_vertex_buffer_object)
+                               rsurface.batchlightmapcolor4f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[4]), rsurface.batchlightmapcolor4f, R_BUFFERDATA_VERTEX, &rsurface.batchlightmapcolor4f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchtexcoordtexture2f && r_batch_dynamicbuffer.integer && vid.support.arb_vertex_buffer_object)
+                               rsurface.batchtexcoordtexture2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordtexture2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordtexture2f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchtexcoordlightmap2f && r_batch_dynamicbuffer.integer && vid.support.arb_vertex_buffer_object)
+                               rsurface.batchtexcoordlightmap2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordlightmap2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordlightmap2f_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchskeletalindex4ub)
+                               rsurface.batchskeletalindex4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalindex4ub_bufferoffset, !vid.forcevbo);
+                       if (rsurface.batchskeletalweight4ub)
+                               rsurface.batchskeletalweight4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalweight4ub_bufferoffset, !vid.forcevbo);
+               }
+               if (rsurface.batchelement3s)
+                       rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset, !vid.forcevbo);
+               else if (rsurface.batchelement3i)
+                       rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset, !vid.forcevbo);
+       }
 }
 
 void RSurf_DrawBatch(void)
@@ -10130,6 +10454,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();
 }
 
@@ -10332,10 +10658,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
                {
@@ -10949,10 +11272,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)
@@ -11044,10 +11364,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();
 }