]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - gl_rmain.c
implemented gpu-skinning (vertex shader skeletal animation), can be
[xonotic/darkplaces.git] / gl_rmain.c
index 3c5177aeee37d434c78869af829a439e27e2d56c..958a341b5a201b60dad5451dc53a2b619301f33d 100644 (file)
@@ -50,8 +50,8 @@ static qboolean r_savedds;
 //
 r_refdef_t r_refdef;
 
-cvar_t r_motionblur = {CVAR_SAVE, "r_motionblur", "0", "screen motionblur - value represents intensity, somewhere around 0.5 recommended"};
-cvar_t r_damageblur = {CVAR_SAVE, "r_damageblur", "0", "screen motionblur based on damage - value represents intensity, somewhere around 0.5 recommended"};
+cvar_t r_motionblur = {CVAR_SAVE, "r_motionblur", "0", "screen motionblur - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"};
+cvar_t r_damageblur = {CVAR_SAVE, "r_damageblur", "0", "screen motionblur based on damage - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"};
 cvar_t r_motionblur_averaging = {CVAR_SAVE, "r_motionblur_averaging", "0.1", "sliding average reaction time for velocity (higher = slower adaption to change)"};
 cvar_t r_motionblur_randomize = {CVAR_SAVE, "r_motionblur_randomize", "0.1", "randomizing coefficient to workaround ghosting"};
 cvar_t r_motionblur_minblur = {CVAR_SAVE, "r_motionblur_minblur", "0.5", "factor of blur to apply at all times (always have this amount of blur no matter what the other factors are)"};
@@ -120,6 +120,7 @@ cvar_t r_shadows_drawafterrtlighting = {CVAR_SAVE, "r_shadows_drawafterrtlightin
 cvar_t r_shadows_castfrombmodels = {CVAR_SAVE, "r_shadows_castfrombmodels", "0", "do cast shadows from bmodels"};
 cvar_t r_shadows_focus = {CVAR_SAVE, "r_shadows_focus", "0 0 0", "offset the shadowed area focus"};
 cvar_t r_shadows_shadowmapscale = {CVAR_SAVE, "r_shadows_shadowmapscale", "1", "increases shadowmap quality (multiply global shadowmap precision) for fake shadows. Needs shadowmapping ON."};
+cvar_t r_shadows_shadowmapbias = {CVAR_SAVE, "r_shadows_shadowmapbias", "-1", "sets shadowmap bias for fake shadows. -1 sets the value of r_shadow_shadowmapping_bias. Needs shadowmapping ON."};
 cvar_t r_q1bsp_skymasking = {0, "r_q1bsp_skymasking", "1", "allows sky polygons in quake1 maps to obscure other geometry"};
 cvar_t r_polygonoffset_submodel_factor = {0, "r_polygonoffset_submodel_factor", "0", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"};
 cvar_t r_polygonoffset_submodel_offset = {0, "r_polygonoffset_submodel_offset", "14", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"};
@@ -132,8 +133,8 @@ cvar_t r_transparentdepthmasking = {CVAR_SAVE, "r_transparentdepthmasking", "0",
 cvar_t r_transparent_sortmindist = {CVAR_SAVE, "r_transparent_sortmindist", "0", "lower distance limit for transparent sorting"};
 cvar_t r_transparent_sortmaxdist = {CVAR_SAVE, "r_transparent_sortmaxdist", "32768", "upper distance limit for transparent sorting"};
 cvar_t r_transparent_sortarraysize = {CVAR_SAVE, "r_transparent_sortarraysize", "4096", "number of distance-sorting layers"};
-cvar_t r_celshading = {CVAR_SAVE, "r_celshading", "0", "cartoon-style light shading"};
-cvar_t r_celoutlines = {CVAR_SAVE, "r_celoutlines", "0", "cartoon-style outlines (requires r_shadow_deferred)"};
+cvar_t r_celshading = {CVAR_SAVE, "r_celshading", "0", "cartoon-style light shading (OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9
+cvar_t r_celoutlines = {CVAR_SAVE, "r_celoutlines", "0", "cartoon-style outlines (requires r_shadow_deferred; OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9
 
 cvar_t gl_fogenable = {0, "gl_fogenable", "0", "nehahra fog enable (for Nehahra compatibility only)"};
 cvar_t gl_fogdensity = {0, "gl_fogdensity", "0.25", "nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only)"};
@@ -161,6 +162,7 @@ cvar_t r_viewscale_fpsscaling_stepsize = {CVAR_SAVE, "r_viewscale_fpsscaling_ste
 cvar_t r_viewscale_fpsscaling_stepmax = {CVAR_SAVE, "r_viewscale_fpsscaling_stepmax", "1.00", "largest adjustment to hit the target framerate (this value prevents wild overshooting of the estimate)"};
 cvar_t r_viewscale_fpsscaling_target = {CVAR_SAVE, "r_viewscale_fpsscaling_target", "70", "desired framerate"};
 
+cvar_t r_glsl_skeletal = {CVAR_SAVE, "r_glsl_skeletal", "1", "render skeletal models faster using a gpu-skinning technique"};
 cvar_t r_glsl_deluxemapping = {CVAR_SAVE, "r_glsl_deluxemapping", "1", "use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps)"};
 cvar_t r_glsl_offsetmapping = {CVAR_SAVE, "r_glsl_offsetmapping", "0", "offset mapping effect (also known as parallax mapping or virtual displacement mapping)"};
 cvar_t r_glsl_offsetmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_steps", "2", "offset mapping steps (note: too high values may be not supported by your GPU)"};
@@ -203,6 +205,7 @@ cvar_t r_bloom_blur = {CVAR_SAVE, "r_bloom_blur", "4", "how large the glow is"};
 cvar_t r_bloom_resolution = {CVAR_SAVE, "r_bloom_resolution", "320", "what resolution to perform the bloom effect at (independent of screen resolution)"};
 cvar_t r_bloom_colorexponent = {CVAR_SAVE, "r_bloom_colorexponent", "1", "how exaggerated the glow is"};
 cvar_t r_bloom_colorsubtract = {CVAR_SAVE, "r_bloom_colorsubtract", "0.125", "reduces bloom colors by a certain amount"};
+cvar_t r_bloom_scenebrightness = {CVAR_SAVE, "r_bloom_scenebrightness", "1", "global rendering brightness when bloom is enabled"};
 
 cvar_t r_hdr_scenebrightness = {CVAR_SAVE, "r_hdr_scenebrightness", "1", "global rendering brightness"};
 cvar_t r_hdr_glowintensity = {CVAR_SAVE, "r_hdr_glowintensity", "1", "how bright light emitting textures should appear"};
@@ -219,14 +222,17 @@ cvar_t r_smoothnormals_areaweighting = {0, "r_smoothnormals_areaweighting", "1",
 
 cvar_t developer_texturelogging = {0, "developer_texturelogging", "0", "produces a textures.log file containing names of skins and map textures the engine tried to load"};
 
-cvar_t gl_lightmaps = {0, "gl_lightmaps", "0", "draws only lightmaps, no texture (for level designers)"};
+cvar_t gl_lightmaps = {0, "gl_lightmaps", "0", "draws only lightmaps, no texture (for level designers), a value of 2 keeps normalmap shading"};
 
 cvar_t r_test = {0, "r_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"};
 
+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_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"};
 
-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."};
+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)"};
 
@@ -657,7 +663,8 @@ shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] =
        {"#define USEBOUNCEGRIDDIRECTIONAL\n", " bouncegriddirectional"}, // TODO make this a static parm
        {"#define USETRIPPY\n", " trippy"},
        {"#define USEDEPTHRGB\n", " depthrgb"},
-       {"#define USEALPHAGENVERTEX\n", "alphagenvertex"}
+       {"#define USEALPHAGENVERTEX\n", " alphagenvertex"},
+       {"#define USESKELETAL\n", " skeletal"}
 };
 
 // NOTE: MUST MATCH ORDER OF SHADERMODE_* ENUMS!
@@ -816,6 +823,7 @@ typedef struct r_glsl_permutation_s
        int loc_ShadowMap_Parameters;
        int loc_ShadowMap_TextureScale;
        int loc_SpecularPower;
+       int loc_Skeletal_Transform12;
        int loc_UserVec1;
        int loc_UserVec2;
        int loc_UserVec3;
@@ -1163,6 +1171,7 @@ 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");
@@ -1534,8 +1543,6 @@ static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, c
                        {
                                if (debugshader)
                                {
-//                                     vsresult = qD3DXPreprocessShader(vertstring, strlen(vertstring), NULL, NULL, &vsbuffer, &vslog);
-//                                     FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_vs.fx", cachename), vsbuffer->GetBufferPointer(), vsbuffer->GetBufferSize());
                                        FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_vs.fx", cachename), vertstring, strlen(vertstring));
                                        vsresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_vs.fx", fs_gamedir, cachename), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable);
                                }
@@ -1543,24 +1550,22 @@ static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, c
                                        vsresult = qD3DXCompileShader(vertstring, strlen(vertstring), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable);
                                if (vsbuffer)
                                {
-                                       vsbinsize = vsbuffer->GetBufferSize();
+                                       vsbinsize = ID3DXBuffer_GetBufferSize(vsbuffer);
                                        vsbin = (DWORD *)Mem_Alloc(tempmempool, vsbinsize);
-                                       memcpy(vsbin, vsbuffer->GetBufferPointer(), vsbinsize);
-                                       vsbuffer->Release();
+                                       memcpy(vsbin, ID3DXBuffer_GetBufferPointer(vsbuffer), vsbinsize);
+                                       ID3DXBuffer_Release(vsbuffer);
                                }
                                if (vslog)
                                {
-                                       strlcpy(temp, (const char *)vslog->GetBufferPointer(), min(sizeof(temp), vslog->GetBufferSize()));
+                                       strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(vslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(vslog)));
                                        Con_DPrintf("HLSL vertex shader compile output for %s follows:\n%s\n", cachename, temp);
-                                       vslog->Release();
+                                       ID3DXBuffer_Release(vslog);
                                }
                        }
                        if (fragstring && fragstring[0])
                        {
                                if (debugshader)
                                {
-//                                     psresult = qD3DXPreprocessShader(fragstring, strlen(fragstring), NULL, NULL, &psbuffer, &pslog);
-//                                     FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_ps.fx", cachename), psbuffer->GetBufferPointer(), psbuffer->GetBufferSize());
                                        FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_ps.fx", cachename), fragstring, strlen(fragstring));
                                        psresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_ps.fx", fs_gamedir, cachename), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable);
                                }
@@ -1568,16 +1573,16 @@ static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, c
                                        psresult = qD3DXCompileShader(fragstring, strlen(fragstring), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable);
                                if (psbuffer)
                                {
-                                       psbinsize = psbuffer->GetBufferSize();
+                                       psbinsize = ID3DXBuffer_GetBufferSize(psbuffer);
                                        psbin = (DWORD *)Mem_Alloc(tempmempool, psbinsize);
-                                       memcpy(psbin, psbuffer->GetBufferPointer(), psbinsize);
-                                       psbuffer->Release();
+                                       memcpy(psbin, ID3DXBuffer_GetBufferPointer(psbuffer), psbinsize);
+                                       ID3DXBuffer_Release(psbuffer);
                                }
                                if (pslog)
                                {
-                                       strlcpy(temp, (const char *)pslog->GetBufferPointer(), min(sizeof(temp), pslog->GetBufferSize()));
+                                       strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(pslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(pslog)));
                                        Con_DPrintf("HLSL pixel shader compile output for %s follows:\n%s\n", cachename, temp);
-                                       pslog->Release();
+                                       ID3DXBuffer_Release(pslog);
                                }
                        }
                        Sys_UnloadLibrary(&d3dx9_dll);
@@ -2144,6 +2149,8 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
        float m16f[16];
        matrix4x4_t tempmatrix;
        r_waterstate_waterplane_t *waterplane = (r_waterstate_waterplane_t *)surfacewaterplane;
+       if (rsurface.entityskeletaltransform3x4)
+               permutation |= SHADERPERMUTATION_SKELETAL;
        if (r_trippy.integer && !notrippy)
                permutation |= SHADERPERMUTATION_TRIPPY;
        if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)
@@ -2182,9 +2189,9 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                else
                {
                        mode = SHADERMODE_GENERIC;
-                       permutation |= SHADERPERMUTATION_DIFFUSE;
-                       GL_BlendFunc(GL_ONE, GL_ZERO);
-                       blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO);
+                       permutation |= SHADERPERMUTATION_DIFFUSE | SHADERPERMUTATION_ALPHAKILL;
+                       GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+                       blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                }
                if (vid.allowalphatocoverage)
                        GL_AlphaToCoverage(false);
@@ -2531,7 +2538,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), texturenumsurfaces, texturesurfacelist);
+               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_SetupShader_SetPermutationHLSL(mode, permutation);
                Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);hlslPSSetParameter16f(D3DPSREGISTER_ModelToReflectCube, m16f);
@@ -2684,7 +2691,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
        case RENDERPATH_GLES2:
                if (!vid.useinterleavedarrays)
                {
-                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist);
+                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
                        R_Mesh_VertexPointer(     3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset);
                        R_Mesh_ColorPointer(      4, GL_FLOAT, sizeof(float[4]), rsurface.batchlightmapcolor4f, rsurface.batchlightmapcolor4f_vertexbuffer, rsurface.batchlightmapcolor4f_bufferoffset);
                        R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset);
@@ -2692,10 +2699,13 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchtvector3f, rsurface.batchtvector3f_vertexbuffer, rsurface.batchtvector3f_bufferoffset);
                        R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchnormal3f, rsurface.batchnormal3f_vertexbuffer, rsurface.batchnormal3f_bufferoffset);
                        R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset);
+                       R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0);
+                       R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, rsurface.batchskeletalindex4ub_vertexbuffer, rsurface.batchskeletalindex4ub_bufferoffset);
+                       R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, rsurface.batchskeletalweight4ub_vertexbuffer, rsurface.batchskeletalweight4ub_bufferoffset);
                }
                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), texturenumsurfaces, texturesurfacelist);
+                       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_SetupShader_SetPermutationGLSL(mode, permutation);
@@ -2839,6 +2849,8 @@ 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.entityskeletalnumtransforms > 0)
+                       qglUniform4fv(r_glsl_permutation->loc_Skeletal_Transform12, rsurface.entityskeletalnumtransforms*3, rsurface.entityskeletaltransform3x4);
                CHECKGLERROR
                break;
        case RENDERPATH_GL11:
@@ -2846,7 +2858,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
        case RENDERPATH_GLES1:
                break;
        case RENDERPATH_SOFT:
-               RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0), texturenumsurfaces, texturesurfacelist);
+               RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
                R_Mesh_PrepareVertices_Mesh_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchsvector3f, rsurface.batchtvector3f, rsurface.batchnormal3f, rsurface.batchlightmapcolor4f, rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordlightmap2f);
                R_SetupShader_SetPermutationSoft(mode, permutation);
                {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, 1, false, m16f);}
@@ -3511,7 +3523,7 @@ skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, co
 
        // if already loaded just return it, otherwise make a new skinframe
        skinframe = R_SkinFrame_Find(name, textureflags, width, height, (textureflags & TEXF_FORCE_RELOAD) ? -1 : skindata ? CRC_Block(skindata, width*height*4) : 0, true);
-       if (skinframe && skinframe->base)
+       if (skinframe->base)
                return skinframe;
        textureflags &= ~TEXF_FORCE_RELOAD;
 
@@ -3581,9 +3593,9 @@ skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, i
 
        // if already loaded just return it, otherwise make a new skinframe
        skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true);
-       if (skinframe && skinframe->base)
+       if (skinframe->base)
                return skinframe;
-       textureflags &= ~TEXF_FORCE_RELOAD;
+       //textureflags &= ~TEXF_FORCE_RELOAD;
 
        skinframe->stain = NULL;
        skinframe->merged = NULL;
@@ -3705,7 +3717,7 @@ skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, co
 
        // if already loaded just return it, otherwise make a new skinframe
        skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true);
-       if (skinframe && skinframe->base)
+       if (skinframe->base)
                return skinframe;
        textureflags &= ~TEXF_FORCE_RELOAD;
 
@@ -4229,6 +4241,7 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_shadows_throwdirection);
        Cvar_RegisterVariable(&r_shadows_focus);
        Cvar_RegisterVariable(&r_shadows_shadowmapscale);
+       Cvar_RegisterVariable(&r_shadows_shadowmapbias);
        Cvar_RegisterVariable(&r_q1bsp_skymasking);
        Cvar_RegisterVariable(&r_polygonoffset_submodel_factor);
        Cvar_RegisterVariable(&r_polygonoffset_submodel_offset);
@@ -4297,6 +4310,7 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_bloom_resolution);
        Cvar_RegisterVariable(&r_bloom_colorexponent);
        Cvar_RegisterVariable(&r_bloom_colorsubtract);
+       Cvar_RegisterVariable(&r_bloom_scenebrightness);
        Cvar_RegisterVariable(&r_hdr_scenebrightness);
        Cvar_RegisterVariable(&r_hdr_glowintensity);
        Cvar_RegisterVariable(&r_hdr_irisadaptation);
@@ -4311,6 +4325,9 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&developer_texturelogging);
        Cvar_RegisterVariable(&gl_lightmaps);
        Cvar_RegisterVariable(&r_test);
+       Cvar_RegisterVariable(&r_batch_multidraw);
+       Cvar_RegisterVariable(&r_batch_multidraw_mintriangles);
+       Cvar_RegisterVariable(&r_glsl_skeletal);
        Cvar_RegisterVariable(&r_glsl_saturation);
        Cvar_RegisterVariable(&r_glsl_saturation_redcompensate);
        Cvar_RegisterVariable(&r_glsl_vertextextureblend_usebothalphas);
@@ -4611,6 +4628,7 @@ void R_AnimCache_ClearCache(void)
                ent->animcache_vertexmesh = NULL;
                ent->animcache_vertex3fbuffer = NULL;
                ent->animcache_vertexmeshbuffer = NULL;
+               ent->animcache_skeletaltransform3x4 = NULL;
        }
 }
 
@@ -4647,6 +4665,106 @@ 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)
+       {
+               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);
+                       }
+               }
+       }
+
        // see if it's already cached this frame
        if (ent->animcache_vertex3f)
        {
@@ -4677,6 +4795,24 @@ qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qbool
                // see if this ent is worth caching
                if (!model || !model->Draw || !model->surfmesh.isanimated || !model->AnimateVertices)
                        return false;
+               // skip entity if the shader backend has a cheaper way
+               if (model->surfmesh.data_skeletalindex4ub && r_glsl_skeletal.integer)
+               {
+                       switch (vid.renderpath)
+                       {
+                       case RENDERPATH_GL20:
+                               return false;
+                       case RENDERPATH_GL11:
+                       case RENDERPATH_GL13:
+                       case RENDERPATH_GLES1:
+                       case RENDERPATH_GLES2:
+                       case RENDERPATH_D3D9:
+                       case RENDERPATH_D3D10:
+                       case RENDERPATH_D3D11:
+                       case RENDERPATH_SOFT:
+                               break;
+                       }
+               }
                // get some memory for this entity and generate mesh data
                numvertices = model->surfmesh.num_vertices;
                ent->animcache_vertex3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices);
@@ -5510,7 +5646,7 @@ static void R_Water_StartFrame(void)
        int i;
        int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight;
        r_waterstate_waterplane_t *p;
-       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.samples < 2;
+       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
 
        if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d)
                return;
@@ -5724,7 +5860,7 @@ static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t
        int planeindex, qualityreduction = 0, old_r_dynamic = 0, old_r_shadows = 0, old_r_worldrtlight = 0, old_r_dlight = 0, old_r_particles = 0, old_r_decals = 0;
        r_waterstate_waterplane_t *p;
        vec3_t visorigin;
-       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.samples < 2;
+       qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
        char vabuf[1024];
 
        originalview = r_refdef.view;
@@ -5996,14 +6132,14 @@ static void R_Bloom_StartFrame(void)
        int i;
        int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight;
        int viewwidth, viewheight;
-       qboolean useviewfbo = r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object && vid.samples < 2;
+       qboolean useviewfbo = r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2;
        textype_t textype = TEXTYPE_COLORBUFFER;
 
        switch (vid.renderpath)
        {
        case RENDERPATH_GL20:
                r_fb.usedepthtextures = r_usedepthtextures.integer != 0;
-               if (vid.support.ext_framebuffer_object)
+               if (vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two)
                {
                        if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F;
                        if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F;
@@ -6378,6 +6514,8 @@ static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortext
        unsigned int permutation;
        float uservecs[4][4];
 
+       R_EntityMatrix(&identitymatrix);
+
        switch (vid.renderpath)
        {
        case RENDERPATH_GL20:
@@ -6918,6 +7056,11 @@ void R_RenderView(void)
        R_Shadow_UpdateWorldLightSelection();
 
        R_Bloom_StartFrame();
+
+       // apply bloom brightness offset
+       if(r_fb.bloomtexture[0])
+               r_refdef.view.colorscale *= r_bloom_scenebrightness.value;
+
        R_Water_StartFrame();
 
        // now we probably have an fbo to render into
@@ -7350,7 +7493,7 @@ static void R_DrawEntityBBoxes(void)
                if(PRVM_serveredictedict(edict, viewmodelforclient) != 0)
                        continue;
                VectorLerp(edict->priv.server->areamins, 0.5f, edict->priv.server->areamaxs, center);
-               R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, center, R_DrawEntityBBoxes_Callback, (entity_render_t *)NULL, i, (rtlight_t *)NULL);
+               R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawEntityBBoxes_Callback, (entity_render_t *)NULL, i, (rtlight_t *)NULL);
        }
 }
 
@@ -7458,12 +7601,12 @@ void R_DrawNoModel(entity_render_t *ent)
        vec3_t org;
        Matrix4x4_OriginFromMatrix(&ent->matrix, org);
        if ((ent->flags & RENDER_ADDITIVE) || (ent->alpha < 1))
-               R_MeshQueue_AddTransparent((ent->flags & RENDER_NODEPTHTEST) ? MESHQUEUE_SORT_HUD : MESHQUEUE_SORT_DISTANCE, org, R_DrawNoModel_TransparentCallback, ent, 0, rsurface.rtlight);
+               R_MeshQueue_AddTransparent((ent->flags & RENDER_NODEPTHTEST) ? TRANSPARENTSORT_HUD : TRANSPARENTSORT_DISTANCE, org, R_DrawNoModel_TransparentCallback, ent, 0, rsurface.rtlight);
        else
                R_DrawNoModel_TransparentCallback(ent, rsurface.rtlight, 0, NULL);
 }
 
-void R_CalcBeam_Vertex3f (float *vert, const vec3_t org1, const vec3_t org2, float width)
+void R_CalcBeam_Vertex3f (float *vert, const float *org1, const float *org2, float width)
 {
        vec3_t right1, right2, diff, normal;
 
@@ -7766,7 +7909,7 @@ texture_t *R_GetCurrentTexture(texture_t *t)
 {
        int i;
        const entity_render_t *ent = rsurface.entity;
-       dp_model_t *model = ent->model;
+       dp_model_t *model = ent->model; // when calling this, ent must not be NULL
        q3shaderinfo_layer_tcmod_t *tcmod;
 
        if (t->update_lastrenderframe == r_textureframe && t->update_lastrenderentity == (void *)ent && !rsurface.forcecurrenttextureupdate)
@@ -7774,7 +7917,7 @@ texture_t *R_GetCurrentTexture(texture_t *t)
        t->update_lastrenderframe = r_textureframe;
        t->update_lastrenderentity = (void *)ent;
 
-       if(ent && ent->entitynumber >= MAX_EDICTS && ent->entitynumber < 2 * MAX_EDICTS)
+       if(ent->entitynumber >= MAX_EDICTS && ent->entitynumber < 2 * MAX_EDICTS)
                t->camera_entity = ent->entitynumber;
        else
                t->camera_entity = 0;
@@ -7965,13 +8108,15 @@ texture_t *R_GetCurrentTexture(texture_t *t)
                t->basetexture = r_texture_grey128;
                t->pantstexture = r_texture_black;
                t->shirttexture = r_texture_black;
-               t->nmaptexture = r_texture_blanknormalmap;
+               if (gl_lightmaps.integer < 2)
+                       t->nmaptexture = r_texture_blanknormalmap;
                t->glosstexture = r_texture_black;
                t->glowtexture = NULL;
                t->fogtexture = NULL;
                t->reflectmasktexture = NULL;
                t->backgroundbasetexture = NULL;
-               t->backgroundnmaptexture = r_texture_blanknormalmap;
+               if (gl_lightmaps.integer < 2)
+                       t->backgroundnmaptexture = r_texture_blanknormalmap;
                t->backgroundglosstexture = r_texture_black;
                t->backgroundglowtexture = NULL;
                t->specularscale = 0;
@@ -8132,6 +8277,12 @@ void RSurf_ActiveWorldEntity(void)
        rsurface.modeltexcoordlightmap2f  = model->surfmesh.data_texcoordlightmap2f;
        rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
        rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f;
+       rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub;
+       rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+       rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub;
+       rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub;
+       rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+       rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub;
        rsurface.modelelement3i = model->surfmesh.data_element3i;
        rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer;
        rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset;
@@ -8172,6 +8323,12 @@ void RSurf_ActiveWorldEntity(void)
        rsurface.batchtexcoordlightmap2f = NULL;
        rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL;
        rsurface.batchtexcoordlightmap2f_bufferoffset = 0;
+       rsurface.batchskeletalindex4ub = NULL;
+       rsurface.batchskeletalindex4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalindex4ub_bufferoffset = 0;
+       rsurface.batchskeletalweight4ub = NULL;
+       rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
        rsurface.batchvertexmeshbuffer = NULL;
        rsurface.batchvertex3fbuffer = NULL;
@@ -8227,7 +8384,10 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
                rsurface.basepolygonfactor += r_polygonoffset_submodel_factor.value;
                rsurface.basepolygonoffset += r_polygonoffset_submodel_offset.value;
        }
-       if (model->surfmesh.isanimated && model->AnimateVertices)
+       // 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.entityskeletalnumtransforms = rsurface.entityskeletaltransform3x4 ? model->num_bones : 0;
+       if (model->surfmesh.isanimated && model->AnimateVertices && !rsurface.entityskeletaltransform3x4)
        {
                if (ent->animcache_vertex3f)
                {
@@ -8310,6 +8470,12 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        rsurface.modeltexcoordlightmap2f  = model->surfmesh.data_texcoordlightmap2f;
        rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
        rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f;
+       rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub;
+       rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+       rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub;
+       rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub;
+       rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer;
+       rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub;
        rsurface.modelelement3i = model->surfmesh.data_element3i;
        rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer;
        rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset;
@@ -8346,6 +8512,12 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        rsurface.batchtexcoordlightmap2f = NULL;
        rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL;
        rsurface.batchtexcoordlightmap2f_bufferoffset = 0;
+       rsurface.batchskeletalindex4ub = NULL;
+       rsurface.batchskeletalindex4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalindex4ub_bufferoffset = 0;
+       rsurface.batchskeletalweight4ub = NULL;
+       rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
        rsurface.batchvertexmeshbuffer = NULL;
        rsurface.batchvertex3fbuffer = NULL;
@@ -8436,6 +8608,12 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
        rsurface.modeltexcoordlightmap2f  = NULL;
        rsurface.modeltexcoordlightmap2f_vertexbuffer = 0;
        rsurface.modeltexcoordlightmap2f_bufferoffset = 0;
+       rsurface.modelskeletalindex4ub = NULL;
+       rsurface.modelskeletalindex4ub_vertexbuffer = NULL;
+       rsurface.modelskeletalindex4ub_bufferoffset = 0;
+       rsurface.modelskeletalweight4ub = NULL;
+       rsurface.modelskeletalweight4ub_vertexbuffer = NULL;
+       rsurface.modelskeletalweight4ub_bufferoffset = 0;
        rsurface.modelelement3i = (int *)element3i;
        rsurface.modelelement3i_indexbuffer = NULL;
        rsurface.modelelement3i_bufferoffset = 0;
@@ -8470,6 +8648,12 @@ void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inve
        rsurface.batchtexcoordlightmap2f = NULL;
        rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL;
        rsurface.batchtexcoordlightmap2f_bufferoffset = 0;
+       rsurface.batchskeletalindex4ub = NULL;
+       rsurface.batchskeletalindex4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalindex4ub_bufferoffset = 0;
+       rsurface.batchskeletalweight4ub = NULL;
+       rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
+       rsurface.batchskeletalweight4ub_bufferoffset = 0;
        rsurface.batchvertexmesh = NULL;
        rsurface.batchvertexmeshbuffer = NULL;
        rsurface.batchvertex3fbuffer = NULL;
@@ -8565,6 +8749,7 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        float scale;
        float center[3], forward[3], right[3], up[3], v[3], newforward[3], newright[3], newup[3];
        float waveparms[4];
+       unsigned char *ub;
        q3shaderinfo_deform_t *deform;
        const msurface_t *surface, *firstsurface;
        r_vertexmesh_t *vertexmesh;
@@ -8611,7 +8796,6 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo)
        {
                dynamicvertex = true;
-               batchneed |= BATCHNEED_NOGAPS;
                needsupdate |= BATCHNEED_VERTEXMESH_VERTEXCOLOR;
        }
 
@@ -8633,36 +8817,36 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                        break;
                case Q3DEFORM_AUTOSPRITE:
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+                       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:
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_NORMAL:
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_WAVE:
                        if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms))
                                break; // if wavefunc is a nop, ignore this transform
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_BULGE:
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR;
                        break;
                case Q3DEFORM_MOVE:
                        if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms))
                                break; // if wavefunc is a nop, ignore this transform
                        dynamicvertex = true;
-                       batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS;
+                       batchneed |= BATCHNEED_ARRAY_VERTEX;
                        needsupdate |= BATCHNEED_VERTEXMESH_VERTEX;
                        break;
                }
@@ -8674,35 +8858,45 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                break;
        case Q3TCGEN_LIGHTMAP:
                dynamicvertex = true;
-               batchneed |= BATCHNEED_ARRAY_LIGHTMAP | BATCHNEED_NOGAPS;
+               batchneed |= BATCHNEED_ARRAY_LIGHTMAP;
                needsupdate |= BATCHNEED_VERTEXMESH_LIGHTMAP;
                break;
        case Q3TCGEN_VECTOR:
                dynamicvertex = true;
-               batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS;
+               batchneed |= BATCHNEED_ARRAY_VERTEX;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
                break;
        case Q3TCGEN_ENVIRONMENT:
                dynamicvertex = true;
-               batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS;
+               batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
                break;
        }
        if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT)
        {
                dynamicvertex = true;
-               batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS;
+               batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD;
                needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD;
        }
 
        if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)))
        {
                dynamicvertex = true;
-               batchneed |= BATCHNEED_NOGAPS;
                needsupdate |= (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP));
        }
 
-       if (dynamicvertex || gaps || rsurface.batchfirstvertex)
+       // when the model data has no vertex buffer (dynamic mesh), we need to
+       // eliminate gaps
+       if (vid.useinterleavedarrays ? !rsurface.modelvertexmeshbuffer : !rsurface.modelvertex3f_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))
+               dynamicvertex = true;
+
+       if (dynamicvertex)
        {
                // when copying, we need to consider the regeneration of vertexmesh, any dependencies it may have must be set...
                if (batchneed & BATCHNEED_VERTEXMESH_VERTEX)      batchneed |= BATCHNEED_ARRAY_VERTEX;
@@ -8711,13 +8905,9 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                if (batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) batchneed |= BATCHNEED_ARRAY_VERTEXCOLOR;
                if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD)    batchneed |= BATCHNEED_ARRAY_TEXCOORD;
                if (batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP)    batchneed |= BATCHNEED_ARRAY_LIGHTMAP;
+               if (batchneed & BATCHNEED_VERTEXMESH_SKELETAL)    batchneed |= BATCHNEED_ARRAY_SKELETAL;
        }
 
-       // when the model data has no vertex buffer (dynamic mesh), we need to
-       // eliminate gaps
-       if (vid.useinterleavedarrays ? !rsurface.modelvertexmeshbuffer : !rsurface.modelvertex3f_vertexbuffer)
-               batchneed |= BATCHNEED_NOGAPS;
-
        // if needsupdate, we have to do a dynamic vertex batch for sure
        if (needsupdate & batchneed)
                dynamicvertex = true;
@@ -8726,11 +8916,6 @@ 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)))
                dynamicvertex = true;
 
-       // if gaps are unacceptable, and there are gaps, it's a dynamic batch...
-       // also some drivers strongly dislike firstvertex
-       if ((batchneed & BATCHNEED_NOGAPS) && (gaps || firstvertex))
-               dynamicvertex = true;
-
        rsurface.batchvertex3f = rsurface.modelvertex3f;
        rsurface.batchvertex3f_vertexbuffer = rsurface.modelvertex3f_vertexbuffer;
        rsurface.batchvertex3f_bufferoffset = rsurface.modelvertex3f_bufferoffset;
@@ -8752,6 +8937,12 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        rsurface.batchtexcoordlightmap2f = rsurface.modeltexcoordlightmap2f;
        rsurface.batchtexcoordlightmap2f_vertexbuffer = rsurface.modeltexcoordlightmap2f_vertexbuffer;
        rsurface.batchtexcoordlightmap2f_bufferoffset = rsurface.modeltexcoordlightmap2f_bufferoffset;
+       rsurface.batchskeletalindex4ub = rsurface.modelskeletalindex4ub;
+       rsurface.batchskeletalindex4ub_vertexbuffer = rsurface.modelskeletalindex4ub_vertexbuffer;
+       rsurface.batchskeletalindex4ub_bufferoffset = rsurface.modelskeletalindex4ub_bufferoffset;
+       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;
@@ -8770,10 +8961,15 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
        // copy the surface list together to avoid wasting upload bandwidth on the
        // vertices in the gaps.
        //
-       // if gaps exist and we have a static vertex buffer, we still have to
-       // combine the index buffer ranges into one dynamic index buffer.
+       // if gaps exist and we have a static vertex buffer, we can choose whether
+       // to combine the index buffer ranges into one dynamic index buffer or
+       // simply issue multiple glDrawElements calls (BATCHNEED_ALLOWMULTIDRAW).
        //
-       // in all cases we end up with data that can be drawn in one call.
+       // in many cases the batch is reduced to one draw call.
+
+       rsurface.batchmultidraw = false;
+       rsurface.batchmultidrawnumsurfaces = 0;
+       rsurface.batchmultidrawsurfacelist = NULL;
 
        if (!dynamicvertex)
        {
@@ -8783,6 +8979,13 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                // otherwise use the original static buffer with an appropriate offset
                if (gaps)
                {
+                       if ((batchneed & BATCHNEED_ALLOWMULTIDRAW) && r_batch_multidraw.integer && batchnumtriangles >= r_batch_multidraw_mintriangles.integer)
+                       {
+                               rsurface.batchmultidraw = true;
+                               rsurface.batchmultidrawnumsurfaces = texturenumsurfaces;
+                               rsurface.batchmultidrawsurfacelist = texturesurfacelist;
+                               return;
+                       }
                        // build a new triangle elements array for this batch
                        rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3]));
                        rsurface.batchfirsttriangle = 0;
@@ -8817,7 +9020,7 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
 
        // now copy the vertex data into a combined array and make an index array
        // (this is what Quake3 does all the time)
-       //if (gaps || rsurface.batchfirstvertex)
+       //if (dynamicvertex)
        {
                rsurface.batchvertex3fbuffer = NULL;
                rsurface.batchvertexmesh = NULL;
@@ -8843,6 +9046,12 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                rsurface.batchtexcoordlightmap2f = NULL;
                rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL;
                rsurface.batchtexcoordlightmap2f_bufferoffset = 0;
+               rsurface.batchskeletalindex4ub = NULL;
+               rsurface.batchskeletalindex4ub_vertexbuffer = NULL;
+               rsurface.batchskeletalindex4ub_bufferoffset = 0;
+               rsurface.batchskeletalweight4ub = NULL;
+               rsurface.batchskeletalweight4ub_vertexbuffer = NULL;
+               rsurface.batchskeletalweight4ub_bufferoffset = 0;
                rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3]));
                rsurface.batchelement3i_indexbuffer = NULL;
                rsurface.batchelement3i_bufferoffset = 0;
@@ -8867,6 +9076,11 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                        rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2]));
                if (batchneed & BATCHNEED_ARRAY_LIGHTMAP)
                        rsurface.batchtexcoordlightmap2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2]));
+               if (batchneed & BATCHNEED_ARRAY_SKELETAL)
+               {
+                       rsurface.batchskeletalindex4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4]));
+                       rsurface.batchskeletalweight4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4]));
+               }
                numvertices = 0;
                numtriangles = 0;
                for (i = 0;i < texturenumsurfaces;i++)
@@ -8928,6 +9142,22 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                                        else
                                                memset(rsurface.batchtexcoordlightmap2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2]));
                                }
+                               if (batchneed & BATCHNEED_ARRAY_SKELETAL)
+                               {
+                                       if (rsurface.modelskeletalindex4ub)
+                                       {
+                                               memcpy(rsurface.batchskeletalindex4ub + 4*numvertices, rsurface.modelskeletalindex4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4]));
+                                               memcpy(rsurface.batchskeletalweight4ub + 4*numvertices, rsurface.modelskeletalweight4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4]));
+                                       }
+                                       else
+                                       {
+                                               memset(rsurface.batchskeletalindex4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4]));
+                                               memset(rsurface.batchskeletalweight4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4]));
+                                               ub = rsurface.batchskeletalweight4ub + 4*numvertices;
+                                               for (j = 0;j < surfacenumvertices;j++)
+                                                       ub[j*4] = 255;
+                                       }
+                               }
                        }
                        RSurf_RenumberElements(rsurface.modelelement3i + 3*surfacefirsttriangle, rsurface.batchelement3i + 3*numtriangles, 3*surfacenumtriangles, numvertices - surfacefirstvertex);
                        numvertices += surfacenumvertices;
@@ -9391,6 +9621,14 @@ void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const
                if ((batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) && rsurface.batchtexcoordlightmap2f)
                        for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++)
                                Vector2Copy(rsurface.batchtexcoordlightmap2f + 2*j, vertexmesh->texcoordlightmap2f);
+               if ((batchneed & BATCHNEED_VERTEXMESH_SKELETAL) && rsurface.batchskeletalindex4ub)
+               {
+                       for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++)
+                       {
+                               Vector4Copy(rsurface.batchskeletalindex4ub + 4*j, vertexmesh->skeletalindex4ub);
+                               Vector4Copy(rsurface.batchskeletalweight4ub + 4*j, vertexmesh->skeletalweight4ub);
+                       }
+               }
        }
 }
 
@@ -9426,7 +9664,31 @@ void RSurf_DrawBatch(void)
                }
        }
 #endif
-       R_Mesh_Draw(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchfirsttriangle, rsurface.batchnumtriangles, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset);
+       if (rsurface.batchmultidraw)
+       {
+               // issue multiple draws rather than copying index data
+               int numsurfaces = rsurface.batchmultidrawnumsurfaces;
+               const msurface_t **surfacelist = rsurface.batchmultidrawsurfacelist;
+               int i, j, k, firstvertex, endvertex, firsttriangle, endtriangle;
+               for (i = 0;i < numsurfaces;)
+               {
+                       // combine consecutive surfaces as one draw
+                       for (k = i, j = i + 1;j < numsurfaces;k = j, j++)
+                               if (surfacelist[j] != surfacelist[k] + 1)
+                                       break;
+                       firstvertex = surfacelist[i]->num_firstvertex;
+                       endvertex = surfacelist[k]->num_firstvertex + surfacelist[k]->num_vertices;
+                       firsttriangle = surfacelist[i]->num_firsttriangle;
+                       endtriangle = surfacelist[k]->num_firsttriangle + surfacelist[k]->num_triangles;
+                       R_Mesh_Draw(firstvertex, endvertex - firstvertex, firsttriangle, endtriangle - firsttriangle, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset);
+                       i = j;
+               }
+       }
+       else
+       {
+               // there is only one consecutive run of index data (may have been combined)
+               R_Mesh_Draw(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchfirsttriangle, rsurface.batchnumtriangles, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset);
+       }
 }
 
 static int RSurf_FindWaterPlaneForSurface(const msurface_t *surface)
@@ -9446,7 +9708,7 @@ static int RSurf_FindWaterPlaneForSurface(const msurface_t *surface)
                d = 0;
                if(!prepared)
                {
-                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, 1, &surface);
+                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, 1, &surface);
                        prepared = true;
                        if(rsurface.batchnumvertices == 0)
                                break;
@@ -9493,7 +9755,7 @@ static void RSurf_DrawBatch_GL11_ApplyFog(void)
                rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4]));
                rsurface.passcolor4f_vertexbuffer = 0;
                rsurface.passcolor4f_bufferoffset = 0;
-               for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4)
+               for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4)
                {
                        f = RSurf_FogVertex(v);
                        c2[0] = c[0] * f;
@@ -9793,7 +10055,7 @@ static void R_DrawTextureSurfaceList_Sky(int texturenumsurfaces, const msurface_
                        // just to make sure that braindead drivers don't draw
                        // anything despite that colormask...
                        GL_BlendFunc(GL_ZERO, GL_ONE);
-                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist);
+                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
                        if (rsurface.batchvertex3fbuffer)
                                R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
                        else
@@ -9971,8 +10233,8 @@ static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, const msurface
                        R_Mesh_TexBind(1, 0);
                        R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0);
                        // generate a color array for the fog pass
-                       R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0);
                        RSurf_DrawBatch_GL11_MakeFogColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3]);
+                       R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0);
                        RSurf_DrawBatch();
                        break;
                default:
@@ -10014,7 +10276,7 @@ static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface
                switch (layer->type)
                {
                case TEXTURELAYERTYPE_LITTEXTURE:
-                       if (layer->blendfunc1 == GL_ONE && layer->blendfunc2 == GL_ZERO)
+                       if (layer->blendfunc1 == GL_ONE && layer->blendfunc2 == GL_ZERO && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST))
                        {
                                // two-pass lit texture with 2x rgbscale
                                // first the lightmap pass
@@ -10047,6 +10309,8 @@ static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface
                                R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset);
                                if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT)
                                        RSurf_DrawBatch_GL11_VertexShade(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog);
+                               else if (FAKELIGHT_ENABLED)
+                                       RSurf_DrawBatch_GL11_FakeLight(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog);
                                else
                                        RSurf_DrawBatch_GL11_VertexColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog);
                        }
@@ -10074,8 +10338,8 @@ static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface
                                R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0);
                        }
                        // generate a color array for the fog pass
-                       R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0);
                        RSurf_DrawBatch_GL11_MakeFogColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3]);
+                       R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0);
                        RSurf_DrawBatch();
                        break;
                default:
@@ -10210,7 +10474,7 @@ static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, const
        {
                RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist);
                batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices);
-               for (j = 0, vi = rsurface.batchfirstvertex;j < rsurface.batchnumvertices;j++, vi++)
+               for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++)
                {
                        VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f);
                        Vector4Set(batchvertex[vi].color4f, 0, 0, 0, 1);
@@ -10222,7 +10486,7 @@ static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, const
        {
                RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist);
                batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices);
-               for (j = 0, vi = rsurface.batchfirstvertex;j < rsurface.batchnumvertices;j++, vi++)
+               for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++)
                {
                        unsigned char c = (vi << 3) * (1.0f / 256.0f);
                        VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f);
@@ -10408,7 +10672,7 @@ static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const
                                R_SetupShader_DepthOrShadow(false, false);
                        }
                        RSurf_SetupDepthAndCulling();
-                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, texturenumsurfaces, texturesurfacelist);
+                       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
                        if (rsurface.batchvertex3fbuffer)
                                R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
                        else
@@ -10492,7 +10756,7 @@ static void R_ProcessTransparentTextureSurfaceList(int texturenumsurfaces, const
                        center[1] += r_refdef.view.forward[1]*rsurface.entity->transparent_offset;
                        center[2] += r_refdef.view.forward[2]*rsurface.entity->transparent_offset;
                }
-               R_MeshQueue_AddTransparent((rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? MESHQUEUE_SORT_HUD : ((rsurface.entity->flags & RENDER_WORLDOBJECT) ? MESHQUEUE_SORT_SKY : MESHQUEUE_SORT_DISTANCE), center, R_DrawSurface_TransparentCallback, rsurface.entity, surface - rsurface.modelsurfaces, rsurface.rtlight);
+               R_MeshQueue_AddTransparent((rsurface.entity->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? TRANSPARENTSORT_HUD : rsurface.texture->transparentsort, center, R_DrawSurface_TransparentCallback, rsurface.entity, surface - rsurface.modelsurfaces, rsurface.rtlight);
        }
 }
 
@@ -10503,7 +10767,7 @@ static void R_DrawTextureSurfaceList_DepthOnly(int texturenumsurfaces, const msu
        if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)))
                return;
        RSurf_SetupDepthAndCulling();
-       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, texturenumsurfaces, texturesurfacelist);
+       RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist);
        if (rsurface.batchvertex3fbuffer)
                R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3fbuffer);
        else
@@ -10739,7 +11003,7 @@ void R_DrawLocs(void)
        for (loc = cl.locnodes, index = 0;loc;loc = loc->next, index++)
        {
                VectorLerp(loc->mins, 0.5f, loc->maxs, center);
-               R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL);
+               R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL);
        }
 }
 
@@ -11184,7 +11448,6 @@ static void R_DrawModelDecals_FadeEntity(entity_render_t *ent)
        else
                frametime = 0;
        decalsystem->lastupdatetime = r_refdef.scene.time;
-       decal = decalsystem->decals;
        numdecals = decalsystem->numdecals;
 
        for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++)
@@ -11261,7 +11524,6 @@ static void R_DrawModelDecals_Entity(entity_render_t *ent)
                RSurf_ActiveModelEntity(ent, false, false, false);
 
        decalsystem->lastupdatetime = r_refdef.scene.time;
-       decal = decalsystem->decals;
 
        faderate = 1.0f / max(0.001f, cl_decals_fadetime.value);
 
@@ -11450,13 +11712,12 @@ static void R_DrawDebugModel(void)
        {
                int triangleindex;
                int bihleafindex;
-               qboolean cullbox = ent == r_refdef.scene.worldentity;
+               qboolean cullbox = false;
                const q3mbrush_t *brush;
                const bih_t *bih = &model->collision_bih;
                const bih_leaf_t *bihleaf;
                float vertex3f[3][3];
                GL_PolygonOffset(r_refdef.polygonfactor + r_showcollisionbrushes_polygonfactor.value, r_refdef.polygonoffset + r_showcollisionbrushes_polygonoffset.value);
-               cullbox = false;
                for (bihleafindex = 0, bihleaf = bih->leafs;bihleafindex < bih->numleafs;bihleafindex++, bihleaf++)
                {
                        if (cullbox && R_CullBox(bihleaf->mins, bihleaf->maxs))
@@ -11859,6 +12120,9 @@ void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, i
        texture.offsetscale = 1;
        texture.specularscalemod = 1;
        texture.specularpowermod = 1;
+       texture.transparentsort = TRANSPARENTSORT_DISTANCE;
+       // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS
+       // JUST GREP FOR "specularscalemod = 1".
 
        surface.texture = &texture;
        surface.num_triangles = numtriangles;