]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - gl_rmain.c
added arg: connect-userinfo; works like this: "connect server password foobar" will...
[xonotic/darkplaces.git] / gl_rmain.c
index 8b75a1c73c31e7d70db4295d544a8bbc4432765c..f5c18f6044f0ac221fdb98b4b956e06b310f76a5 100644 (file)
@@ -25,6 +25,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "polygon.h"
 #include "image.h"
 #include "ft2.h"
+#include "csprogs.h"
+#include "cl_video.h"
 
 mempool_t *r_main_mempool;
 rtexturepool_t *r_main_texturepool;
@@ -74,7 +76,10 @@ cvar_t r_showcollisionbrushes_polygonoffset = {0, "r_showcollisionbrushes_polygo
 cvar_t r_showdisabledepthtest = {0, "r_showdisabledepthtest", "0", "disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing"};
 cvar_t r_drawportals = {0, "r_drawportals", "0", "shows portals (separating polygons) in world interior in quake1 maps"};
 cvar_t r_drawentities = {0, "r_drawentities","1", "draw entities (doors, players, projectiles, etc)"};
+cvar_t r_draw2d = {0, "r_draw2d","1", "draw 2D stuff (dangerous to turn off)"};
+cvar_t r_drawworld = {0, "r_drawworld","1", "draw world (most static stuff)"};
 cvar_t r_drawviewmodel = {0, "r_drawviewmodel","1", "draw your weapon model"};
+cvar_t r_drawexteriormodel = {0, "r_drawexteriormodel","1", "draw your player model (e.g. in chase cam, reflections)"};
 cvar_t r_cullentities_trace = {0, "r_cullentities_trace", "1", "probabistically cull invisible entities"};
 cvar_t r_cullentities_trace_samples = {0, "r_cullentities_trace_samples", "2", "number of samples to test for entity culling (in addition to center sample)"};
 cvar_t r_cullentities_trace_tempentitysamples = {0, "r_cullentities_trace_tempentitysamples", "-1", "number of samples to test for entity culling of temp entities (including all CSQC entities), -1 disables trace culling on these entities to prevent flicker (pvs still applies)"};
@@ -216,6 +221,7 @@ rtexture_t *r_texture_notexture;
 rtexture_t *r_texture_whitecube;
 rtexture_t *r_texture_normalizationcube;
 rtexture_t *r_texture_fogattenuation;
+rtexture_t *r_texture_fogheighttexture;
 rtexture_t *r_texture_gammaramps;
 unsigned int r_texture_gammaramps_serial;
 //rtexture_t *r_texture_fogintensity;
@@ -302,6 +308,7 @@ void FOG_clear(void)
        r_refdef.fog_end = 16384;
        r_refdef.fog_height = 1<<30;
        r_refdef.fog_fadedepth = 128;
+       memset(r_refdef.fog_height_texturename, 0, sizeof(r_refdef.fog_height_texturename));
 }
 
 static void R_BuildBlankTextures(void)
@@ -311,22 +318,22 @@ static void R_BuildBlankTextures(void)
        data[1] = 128; // normal Y
        data[0] = 255; // normal Z
        data[3] = 128; // height
-       r_texture_blanknormalmap = R_LoadTexture2D(r_main_texturepool, "blankbump", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, NULL);
+       r_texture_blanknormalmap = R_LoadTexture2D(r_main_texturepool, "blankbump", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL);
        data[0] = 255;
        data[1] = 255;
        data[2] = 255;
        data[3] = 255;
-       r_texture_white = R_LoadTexture2D(r_main_texturepool, "blankwhite", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, NULL);
+       r_texture_white = R_LoadTexture2D(r_main_texturepool, "blankwhite", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL);
        data[0] = 128;
        data[1] = 128;
        data[2] = 128;
        data[3] = 255;
-       r_texture_grey128 = R_LoadTexture2D(r_main_texturepool, "blankgrey128", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, NULL);
+       r_texture_grey128 = R_LoadTexture2D(r_main_texturepool, "blankgrey128", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL);
        data[0] = 0;
        data[1] = 0;
        data[2] = 0;
        data[3] = 255;
-       r_texture_black = R_LoadTexture2D(r_main_texturepool, "blankblack", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, NULL);
+       r_texture_black = R_LoadTexture2D(r_main_texturepool, "blankblack", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL);
 }
 
 static void R_BuildNoTexture(void)
@@ -354,14 +361,14 @@ static void R_BuildNoTexture(void)
                        }
                }
        }
-       r_texture_notexture = R_LoadTexture2D(r_main_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_PERSISTENT, NULL);
+       r_texture_notexture = R_LoadTexture2D(r_main_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_PERSISTENT, -1, NULL);
 }
 
 static void R_BuildWhiteCube(void)
 {
        unsigned char data[6*1*1*4];
        memset(data, 255, sizeof(data));
-       r_texture_whitecube = R_LoadTextureCubeMap(r_main_texturepool, "whitecube", 1, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, NULL);
+       r_texture_whitecube = R_LoadTextureCubeMap(r_main_texturepool, "whitecube", 1, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL);
 }
 
 static void R_BuildNormalizationCube(void)
@@ -422,7 +429,7 @@ static void R_BuildNormalizationCube(void)
                        }
                }
        }
-       r_texture_normalizationcube = R_LoadTextureCubeMap(r_main_texturepool, "normalcube", NORMSIZE, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, NULL);
+       r_texture_normalizationcube = R_LoadTextureCubeMap(r_main_texturepool, "normalcube", NORMSIZE, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL);
        Mem_Free(data);
 }
 
@@ -477,11 +484,79 @@ static void R_BuildFogTexture(void)
        }
        else
        {
-               r_texture_fogattenuation = R_LoadTexture2D(r_main_texturepool, "fogattenuation", FOGWIDTH, 1, &data1[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT | TEXF_ALLOWUPDATES, NULL);
-               //r_texture_fogintensity = R_LoadTexture2D(r_main_texturepool, "fogintensity", FOGWIDTH, 1, &data2[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_ALLOWUPDATES, NULL);
+               r_texture_fogattenuation = R_LoadTexture2D(r_main_texturepool, "fogattenuation", FOGWIDTH, 1, &data1[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL);
+               //r_texture_fogintensity = R_LoadTexture2D(r_main_texturepool, "fogintensity", FOGWIDTH, 1, &data2[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP, NULL);
        }
 }
 
+static void R_BuildFogHeightTexture(void)
+{
+       unsigned char *inpixels;
+       int size;
+       int x;
+       int y;
+       int j;
+       float c[4];
+       float f;
+       inpixels = NULL;
+       strlcpy(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename, sizeof(r_refdef.fogheighttexturename));
+       if (r_refdef.fogheighttexturename[0])
+               inpixels = loadimagepixelsbgra(r_refdef.fogheighttexturename, true, false, false, NULL);
+       if (!inpixels)
+       {
+               r_refdef.fog_height_tablesize = 0;
+               if (r_texture_fogheighttexture)
+                       R_FreeTexture(r_texture_fogheighttexture);
+               r_texture_fogheighttexture = NULL;
+               if (r_refdef.fog_height_table2d)
+                       Mem_Free(r_refdef.fog_height_table2d);
+               r_refdef.fog_height_table2d = NULL;
+               if (r_refdef.fog_height_table1d)
+                       Mem_Free(r_refdef.fog_height_table1d);
+               r_refdef.fog_height_table1d = NULL;
+               return;
+       }
+       size = image_width;
+       r_refdef.fog_height_tablesize = size;
+       r_refdef.fog_height_table1d = Mem_Alloc(r_main_mempool, size * 4);
+       r_refdef.fog_height_table2d = Mem_Alloc(r_main_mempool, size * size * 4);
+       memcpy(r_refdef.fog_height_table1d, inpixels, size * 4);
+       Mem_Free(inpixels);
+       // LordHavoc: now the magic - what is that table2d for?  it is a cooked
+       // average fog color table accounting for every fog layer between a point
+       // and the camera.  (Note: attenuation is handled separately!)
+       for (y = 0;y < size;y++)
+       {
+               for (x = 0;x < size;x++)
+               {
+                       Vector4Clear(c);
+                       f = 0;
+                       if (x < y)
+                       {
+                               for (j = x;j <= y;j++)
+                               {
+                                       Vector4Add(c, r_refdef.fog_height_table1d + j*4, c);
+                                       f++;
+                               }
+                       }
+                       else
+                       {
+                               for (j = x;j >= y;j--)
+                               {
+                                       Vector4Add(c, r_refdef.fog_height_table1d + j*4, c);
+                                       f++;
+                               }
+                       }
+                       f = 1.0f / f;
+                       r_refdef.fog_height_table2d[(y*size+x)*4+0] = (unsigned char)(c[0] * f);
+                       r_refdef.fog_height_table2d[(y*size+x)*4+1] = (unsigned char)(c[1] * f);
+                       r_refdef.fog_height_table2d[(y*size+x)*4+2] = (unsigned char)(c[2] * f);
+                       r_refdef.fog_height_table2d[(y*size+x)*4+3] = (unsigned char)(c[3] * f);
+               }
+       }
+       r_texture_fogheighttexture = R_LoadTexture2D(r_main_texturepool, "fogheighttable", size, size, r_refdef.fog_height_table2d, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP, -1, NULL);
+}
+
 //=======================================================================================================================================================
 
 static const char *builtinshaderstring =
@@ -489,7 +564,7 @@ static const char *builtinshaderstring =
 "// written by Forest 'LordHavoc' Hale\n"
 "// shadowmapping enhancements by Lee 'eihrul' Salzman\n"
 "\n"
-"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE)\n"
+"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n"
 "# define USEFOG\n"
 "#endif\n"
 "#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n"
@@ -603,7 +678,7 @@ static const char *builtinshaderstring =
 "#endif\n"
 "//uncomment these if you want to use them:\n"
 "uniform vec4 UserVec1;\n"
-"// uniform vec4 UserVec2;\n"
+"uniform vec4 UserVec2;\n"
 "// uniform vec4 UserVec3;\n"
 "// uniform vec4 UserVec4;\n"
 "// uniform float ClientTime;\n"
@@ -621,12 +696,42 @@ static const char *builtinshaderstring =
 "#ifdef USEPOSTPROCESSING\n"
 "// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n"
 "// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n"
+"      float sobel = 1.0;\n"
+"      // vec2 ts = textureSize(Texture_First, 0);\n"
+"      // vec2 px = vec2(1/ts.x, 1/ts.y);\n"
+"      vec2 px = PixelSize;\n"
+"      vec3 x1 = texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n"
+"      vec3 x2 = texture2D(Texture_First, TexCoord1 + vec2(-px.x,  0.0)).rgb;\n"
+"      vec3 x3 = texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n"
+"      vec3 x4 = texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n"
+"      vec3 x5 = texture2D(Texture_First, TexCoord1 + vec2( px.x,  0.0)).rgb;\n"
+"      vec3 x6 = texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n"
+"      vec3 y1 = texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n"
+"      vec3 y2 = texture2D(Texture_First, TexCoord1 + vec2(  0.0,-px.y)).rgb;\n"
+"      vec3 y3 = texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n"
+"      vec3 y4 = texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n"
+"      vec3 y5 = texture2D(Texture_First, TexCoord1 + vec2(  0.0, px.y)).rgb;\n"
+"      vec3 y6 = texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n"
+"      float px1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x1);\n"
+"      float px2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), x2);\n"
+"      float px3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x3);\n"
+"      float px4 =  1.0 * dot(vec3(0.3, 0.59, 0.11), x4);\n"
+"      float px5 =  2.0 * dot(vec3(0.3, 0.59, 0.11), x5);\n"
+"      float px6 =  1.0 * dot(vec3(0.3, 0.59, 0.11), x6);\n"
+"      float py1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y1);\n"
+"      float py2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), y2);\n"
+"      float py3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y3);\n"
+"      float py4 =  1.0 * dot(vec3(0.3, 0.59, 0.11), y4);\n"
+"      float py5 =  2.0 * dot(vec3(0.3, 0.59, 0.11), y5);\n"
+"      float py6 =  1.0 * dot(vec3(0.3, 0.59, 0.11), y6);\n"
+"      sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n"
 "      gl_FragColor += texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.987688, -0.156434)) * UserVec1.y;\n"
 "      gl_FragColor += texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.156434, -0.891007)) * UserVec1.y;\n"
 "      gl_FragColor += texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.891007, -0.453990)) * UserVec1.y;\n"
 "      gl_FragColor += texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.707107,  0.707107)) * UserVec1.y;\n"
 "      gl_FragColor += texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.453990,  0.891007)) * UserVec1.y;\n"
-"      gl_FragColor /= (1 + 5 * UserVec1.y);\n"
+"      gl_FragColor /= (1.0 + 5.0 * UserVec1.y);\n"
+"      gl_FragColor.rgb = gl_FragColor.rgb * (1.0 + UserVec2.x) + vec3(max(0.0, sobel - UserVec2.z))*UserVec2.y;\n"
 "#endif\n"
 "\n"
 "#ifdef USESATURATION\n"
@@ -928,6 +1033,9 @@ static const char *builtinshaderstring =
 "uniform sampler2D Texture_Shirt;\n"
 "#endif\n"
 "#ifdef USEFOG\n"
+"#ifdef USEFOGHEIGHTTEXTURE\n"
+"uniform sampler2D Texture_FogHeightTexture;\n"
+"#endif\n"
 "uniform sampler2D Texture_FogMask;\n"
 "#endif\n"
 "#ifdef USELIGHTMAP\n"
@@ -957,17 +1065,23 @@ static const char *builtinshaderstring =
 "uniform float FogRangeRecip;\n"
 "uniform float FogPlaneViewDist;\n"
 "uniform float FogHeightFade;\n"
-"float FogVertex(void)\n"
+"vec3 FogVertex(vec3 surfacecolor)\n"
 "{\n"
 "      vec3 EyeVectorModelSpace = EyeVectorModelSpaceFogPlaneVertexDist.xyz;\n"
 "      float FogPlaneVertexDist = EyeVectorModelSpaceFogPlaneVertexDist.w;\n"
 "      float fogfrac;\n"
-"#ifdef USEFOGOUTSIDE\n"
-"      fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n"
+"#ifdef USEFOGHEIGHTTEXTURE\n"
+"      vec4 fogheightpixel = texture2D(Texture_FogHeightTexture, vec2(1,1) + vec2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n"
+"      fogfrac = fogheightpixel.a;\n"
+"      return mix(fogheightpixel.rgb * FogColor, surfacecolor, texture2D(Texture_FogMask, myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n"
 "#else\n"
+"# ifdef USEFOGOUTSIDE\n"
+"      fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n"
+"# else\n"
 "      fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n"
+"# endif\n"
+"      return mix(FogColor, surfacecolor, texture2D(Texture_FogMask, myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n"
 "#endif\n"
-"      return float(texture2D(Texture_FogMask, myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)));\n"
 "}\n"
 "#endif\n"
 "\n"
@@ -1088,7 +1202,7 @@ static const char *builtinshaderstring =
 "vec4 GetShadowMapTCCube(vec3 dir)\n"
 "{\n"
 "      vec3 adir = abs(dir);\n"
-"      return vec4(dir, ShadowMap_Parameters.z + ShadowMap_Parameters.w / max(max(adir.x, adir.y), adir.z));\n"
+"      return vec4(dir, ShadowMap_Parameters.w + ShadowMap_Parameters.y / max(max(adir.x, adir.y), adir.z));\n"
 "}\n"
 "#endif\n"
 "\n"
@@ -1682,11 +1796,7 @@ static const char *builtinshaderstring =
 "#endif\n"
 "\n"
 "#ifdef USEFOG\n"
-"#ifdef MODE_LIGHTSOURCE\n"
-"      color.rgb *= myhalf(FogVertex());\n"
-"#else\n"
-"      color.rgb = mix(FogColor, color.rgb, FogVertex());\n"
-"#endif\n"
+"      color.rgb = FogVertex(color.rgb);\n"
 "#endif\n"
 "\n"
 "      // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n"
@@ -1756,7 +1866,12 @@ const char *builtincgshaderstring =
 "// written by Forest 'LordHavoc' Hale\n"
 "// shadowmapping enhancements by Lee 'eihrul' Salzman\n"
 "\n"
-"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE)\n"
+"// FIXME: we need to get rid of ModelViewProjectionPosition to make room for the texcoord for this\n"
+"#if defined(USEREFLECTION)\n"
+"#undef USESHADOWMAPORTHO\n"
+"#endif\n"
+"\n"
+"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n"
 "# define USEFOG\n"
 "#endif\n"
 "#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n"
@@ -1766,6 +1881,10 @@ const char *builtincgshaderstring =
 "#define USEEYEVECTOR\n"
 "#endif\n"
 "\n"
+"#ifdef FRAGMENT_SHADER\n"
+"#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n"
+"#endif\n"
+"\n"
 "#ifdef MODE_DEPTH_OR_SHADOW\n"
 "#ifdef VERTEX_SHADER\n"
 "void main\n"
@@ -2143,15 +2262,21 @@ const char *builtincgshaderstring =
 "#ifdef FRAGMENT_SHADER\n"
 "\n"
 "#ifdef USEFOG\n"
-"float FogVertex(float3 EyeVectorModelSpace, float FogPlaneVertexDist, float FogRangeRecip, float FogPlaneViewDist, float FogHeightFade, sampler2D Texture_FogMask)\n"
+"float3 FogVertex(float3 surfacecolor, float3 FogColor, float3 EyeVectorModelSpace, float FogPlaneVertexDist, float FogRangeRecip, float FogPlaneViewDist, float FogHeightFade, sampler2D Texture_FogMask, sampler2D Texture_FogHeightTexture)\n"
 "{\n"
 "      float fogfrac;\n"
-"#ifdef USEFOGOUTSIDE\n"
-"      fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n"
+"#ifdef USEFOGHEIGHTTEXTURE\n"
+"      float4 fogheightpixel = tex2D(Texture_FogHeightTexture, float2(1,1) + float2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n"
+"      fogfrac = fogheightpixel.a;\n"
+"      return lerp(fogheightpixel.rgb * FogColor, surfacecolor, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n"
 "#else\n"
+"# ifdef USEFOGOUTSIDE\n"
+"      fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n"
+"# else\n"
 "      fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n"
+"# endif\n"
+"      return lerp(FogColor, surfacecolor, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n"
 "#endif\n"
-"      return float(tex2D(Texture_FogMask, half2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)));\n"
 "}\n"
 "#endif\n"
 "\n"
@@ -2211,7 +2336,7 @@ const char *builtincgshaderstring =
 "      float3 adir = abs(dir);\n"
 "      float2 aparams = ShadowMap_Parameters.xy / max(max(adir.x, adir.y), adir.z);\n"
 "      float4 proj = texCUBE(Texture_CubeProjection, dir);\n"
-"      return float3(lerp(dir.xy, proj.xy, dir.zz) * aparams.x + proj.zw * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n"
+"      return float3(lerp(dir.xy, dir.zz, proj.xy) * aparams.x + proj.zw * ShadowMap_Parameters.z, aparams.y + ShadowMap_Parameters.w);\n"
 "}\n"
 "#  else\n"
 "float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters)\n"
@@ -2226,13 +2351,13 @@ const char *builtincgshaderstring =
 "}\n"
 "#  endif\n"
 "# endif\n"
-"#endif // defined(USESHADOWMAPRECT) || defined(USESHADOWMAP2D) || defined(USESHADOWMAPORTHO)\n"
+"#endif // defined(USESHADOWMAPRECT) || defined(USESHADOWMAP2D)\n"
 "\n"
 "#ifdef USESHADOWMAPCUBE\n"
 "float4 GetShadowMapTCCube(float3 dir, float4 ShadowMap_Parameters)\n"
 "{\n"
 "    float3 adir = abs(dir);\n"
-"    return float4(dir, ShadowMap_Parameters.z + ShadowMap_Parameters.w / max(max(adir.x, adir.y), adir.z));\n"
+"    return float4(dir, ShadowMap_Parameters.w + ShadowMap_Parameters.y / max(max(adir.x, adir.y), adir.z));\n"
 "}\n"
 "#endif\n"
 "\n"
@@ -2318,40 +2443,19 @@ const char *builtincgshaderstring =
 "#    ifdef USESHADOWMAPPCF\n"
 "#     if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n"
 "#      ifdef GL_ARB_texture_gather\n"
-"#        define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, ivec2(x, y))\n"
-"#      else\n"
-"#        define texval(x, y) texture4(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale)\n"
-"#      endif\n"
-"      float2 offset = frac(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n"
-"#      if USESHADOWMAPPCF > 1\n"
-"      float4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n"
-"      float4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n"
-"      float4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n"
-"      float4 group4 = step(shadowmaptc.z, texval(-2.0,  0.0));\n"
-"      float4 group5 = step(shadowmaptc.z, texval( 0.0,  0.0));\n"
-"      float4 group6 = step(shadowmaptc.z, texval( 2.0,  0.0));\n"
-"      float4 group7 = step(shadowmaptc.z, texval(-2.0,  2.0));\n"
-"      float4 group8 = step(shadowmaptc.z, texval( 0.0,  2.0));\n"
-"      float4 group9 = step(shadowmaptc.z, texval( 2.0,  2.0));\n"
-"      float4 locols = float4(group1.ab, group3.ab);\n"
-"      float4 hicols = float4(group7.rg, group9.rg);\n"
-"      locols.yz += group2.ab;\n"
-"      hicols.yz += group8.rg;\n"
-"      float4 midcols = float4(group1.rg, group3.rg) + float4(group7.ab, group9.ab) +\n"
-"                  float4(group4.rg, group6.rg) + float4(group4.ab, group6.ab) +\n"
-"                  lerp(locols, hicols, offset.y);\n"
-"      float4 cols = group5 + float4(group2.rg, group8.ab);\n"
-"      cols.xyz += lerp(midcols.xyz, midcols.yzw, offset.x);\n"
-"      f = dot(cols, float4(1.0/25.0));\n"
+"#        define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, ivec(x, y))\n"
 "#      else\n"
-"      float4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n"
-"      float4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n"
-"      float4 group3 = step(shadowmaptc.z, texval(-1.0,  1.0));\n"
-"      float4 group4 = step(shadowmaptc.z, texval( 1.0,  1.0));\n"
-"      float4 cols = float4(group1.rg, group2.rg) + float4(group3.ab, group4.ab) +\n"
-"                              lerp(float4(group1.ab, group2.ab), float4(group3.rg, group4.rg), offset.y);\n"
-"      f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n"
+"#        define texval(x, y) texture4(Texture_ShadowMap2D, center + float2(x,y)*ShadowMap_TextureScale)\n"
 "#      endif\n"
+"    float2 center = shadowmaptc.xy - 0.5, offset = frac(center);\n"
+"    center *= ShadowMap_TextureScale;\n"
+"    float4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n"
+"    float4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n"
+"    float4 group3 = step(shadowmaptc.z, texval(-1.0,  1.0));\n"
+"    float4 group4 = step(shadowmaptc.z, texval( 1.0,  1.0));\n"
+"    float4 cols = float4(group1.rg, group2.rg) + float4(group3.ab, group4.ab) +\n"
+"                lerp(float4(group1.ab, group2.ab), float4(group3.rg, group4.rg), offset.y);\n"
+"    f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n"
 "#     else\n"
 "#        define texval(x, y) texDepth2D(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale)  \n"
 "#      if USESHADOWMAPPCF > 1\n"
@@ -2551,6 +2655,10 @@ const char *builtincgshaderstring =
 "uniform sampler2D Texture_ScreenDepth,\n"
 "uniform sampler2D Texture_ScreenNormalMap,\n"
 "\n"
+"#ifdef USECUBEFILTER\n"
+"uniform samplerCUBE Texture_Cube,\n"
+"#endif\n"
+"\n"
 "#ifdef USESHADOWMAPRECT\n"
 "# ifdef USESHADOWSAMPLER\n"
 "uniform samplerRECTShadow Texture_ShadowMapRect,\n"
@@ -2590,7 +2698,7 @@ const char *builtincgshaderstring =
 "{\n"
 "      // calculate viewspace pixel position\n"
 "      float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n"
-"      ScreenTexCoord.y = ScreenTexCoord.y * -1 + 1; // Cg is opposite?\n"
+"      //ScreenTexCoord.y = ScreenTexCoord.y * -1 + 1; // Cg is opposite?\n"
 "      float3 position;\n"
 "      position.z = ScreenToDepth.y / (texDepth2D(Texture_ScreenDepth, ScreenTexCoord) + ScreenToDepth.x);\n"
 "      position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n"
@@ -2722,7 +2830,7 @@ const char *builtincgshaderstring =
 "out float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n"
 "#endif\n"
 "#ifdef USESHADOWMAPORTHO\n"
-"out float3 ShadowMapTC : TEXCOORD8,\n"
+"out float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n"
 "#endif\n"
 "out float4 gl_Position : POSITION\n"
 ")\n"
@@ -2831,7 +2939,7 @@ const char *builtincgshaderstring =
 "float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n"
 "#endif\n"
 "#ifdef USESHADOWMAPORTHO\n"
-"float3 ShadowMapTC : TEXCOORD8,\n"
+"float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n"
 "#endif\n"
 "\n"
 "uniform sampler2D Texture_Normal,\n"
@@ -2857,6 +2965,7 @@ const char *builtincgshaderstring =
 "uniform sampler2D Texture_Shirt,\n"
 "#endif\n"
 "#ifdef USEFOG\n"
+"uniform sampler2D Texture_FogHeightTexture,\n"
 "uniform sampler2D Texture_FogMask,\n"
 "#endif\n"
 "#ifdef USELIGHTMAP\n"
@@ -3166,11 +3275,7 @@ const char *builtincgshaderstring =
 "#endif\n"
 "\n"
 "#ifdef USEFOG\n"
-"#ifdef MODE_LIGHTSOURCE\n"
-"      color.rgb *= half(FogVertex(EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask));\n"
-"#else\n"
-"      color.rgb = lerp(FogColor, float3(color.rgb), FogVertex(EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask));\n"
-"#endif\n"
+"      color.rgb = FogVertex(color.rgb, FogColor, EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask, Texture_FogHeightTexture);\n"
 "#endif\n"
 "\n"
 "      // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n"
@@ -3238,29 +3343,30 @@ typedef enum shaderpermutation_e
        SHADERPERMUTATION_SATURATION = 1<<4, ///< saturation (postprocessing only)
        SHADERPERMUTATION_FOGINSIDE = 1<<5, ///< tint the color by fog color or black if using additive blend mode
        SHADERPERMUTATION_FOGOUTSIDE = 1<<6, ///< tint the color by fog color or black if using additive blend mode
-       SHADERPERMUTATION_GAMMARAMPS = 1<<7, ///< gamma (postprocessing only)
-       SHADERPERMUTATION_CUBEFILTER = 1<<8, ///< (lightsource) use cubemap light filter
-       SHADERPERMUTATION_GLOW = 1<<9, ///< (lightmap) blend in an additive glow texture
-       SHADERPERMUTATION_BLOOM = 1<<10, ///< bloom (postprocessing only)
-       SHADERPERMUTATION_SPECULAR = 1<<11, ///< (lightsource or deluxemapping) render specular effects
-       SHADERPERMUTATION_POSTPROCESSING = 1<<12, ///< user defined postprocessing (postprocessing only)
-       SHADERPERMUTATION_EXACTSPECULARMATH = 1<<13, ///< (lightsource or deluxemapping) use exact reflection map for specular effects, as opposed to the usual OpenGL approximation
-       SHADERPERMUTATION_REFLECTION = 1<<14, ///< normalmap-perturbed reflection of the scene infront of the surface, preformed as an overlay on the surface
-       SHADERPERMUTATION_OFFSETMAPPING = 1<<15, ///< adjust texcoords to roughly simulate a displacement mapped surface
-       SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING = 1<<16, ///< adjust texcoords to accurately simulate a displacement mapped surface (requires OFFSETMAPPING to also be set!)
-       SHADERPERMUTATION_SHADOWMAPRECT = 1<<17, ///< (lightsource) use shadowmap rectangle texture as light filter
-       SHADERPERMUTATION_SHADOWMAPCUBE = 1<<18, ///< (lightsource) use shadowmap cubemap texture as light filter
-       SHADERPERMUTATION_SHADOWMAP2D = 1<<19, ///< (lightsource) use shadowmap rectangle texture as light filter
-       SHADERPERMUTATION_SHADOWMAPPCF = 1<<20, ///< (lightsource) use percentage closer filtering on shadowmap test results
-       SHADERPERMUTATION_SHADOWMAPPCF2 = 1<<21, ///< (lightsource) use higher quality percentage closer filtering on shadowmap test results
-       SHADERPERMUTATION_SHADOWSAMPLER = 1<<22, ///< (lightsource) use hardware shadowmap test
-       SHADERPERMUTATION_SHADOWMAPVSDCT = 1<<23, ///< (lightsource) use virtual shadow depth cube texture for shadowmap indexing
-       SHADERPERMUTATION_SHADOWMAPORTHO = 1<<24, //< (lightsource) use orthographic shadowmap projection
-       SHADERPERMUTATION_DEFERREDLIGHTMAP = 1<<25, ///< (lightmap) read Texture_ScreenDiffuse/Specular textures and add them on top of lightmapping
-       SHADERPERMUTATION_ALPHAKILL = 1<<26, ///< (deferredgeometry) discard pixel if diffuse texture alpha below 0.5
-       SHADERPERMUTATION_REFLECTCUBE = 1<<27, ///< fake reflections using global cubemap (not HDRI light probe)
-       SHADERPERMUTATION_LIMIT = 1<<28, ///< size of permutations array
-       SHADERPERMUTATION_COUNT = 28 ///< size of shaderpermutationinfo array
+       SHADERPERMUTATION_FOGHEIGHTTEXTURE = 1<<7, ///< fog color and density determined by texture mapped on vertical axis
+       SHADERPERMUTATION_GAMMARAMPS = 1<<8, ///< gamma (postprocessing only)
+       SHADERPERMUTATION_CUBEFILTER = 1<<9, ///< (lightsource) use cubemap light filter
+       SHADERPERMUTATION_GLOW = 1<<10, ///< (lightmap) blend in an additive glow texture
+       SHADERPERMUTATION_BLOOM = 1<<11, ///< bloom (postprocessing only)
+       SHADERPERMUTATION_SPECULAR = 1<<12, ///< (lightsource or deluxemapping) render specular effects
+       SHADERPERMUTATION_POSTPROCESSING = 1<<13, ///< user defined postprocessing (postprocessing only)
+       SHADERPERMUTATION_EXACTSPECULARMATH = 1<<14, ///< (lightsource or deluxemapping) use exact reflection map for specular effects, as opposed to the usual OpenGL approximation
+       SHADERPERMUTATION_REFLECTION = 1<<15, ///< normalmap-perturbed reflection of the scene infront of the surface, preformed as an overlay on the surface
+       SHADERPERMUTATION_OFFSETMAPPING = 1<<16, ///< adjust texcoords to roughly simulate a displacement mapped surface
+       SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING = 1<<17, ///< adjust texcoords to accurately simulate a displacement mapped surface (requires OFFSETMAPPING to also be set!)
+       SHADERPERMUTATION_SHADOWMAPRECT = 1<<18, ///< (lightsource) use shadowmap rectangle texture as light filter
+       SHADERPERMUTATION_SHADOWMAPCUBE = 1<<19, ///< (lightsource) use shadowmap cubemap texture as light filter
+       SHADERPERMUTATION_SHADOWMAP2D = 1<<20, ///< (lightsource) use shadowmap rectangle texture as light filter
+       SHADERPERMUTATION_SHADOWMAPPCF = 1<<21, ///< (lightsource) use percentage closer filtering on shadowmap test results
+       SHADERPERMUTATION_SHADOWMAPPCF2 = 1<<22, ///< (lightsource) use higher quality percentage closer filtering on shadowmap test results
+       SHADERPERMUTATION_SHADOWSAMPLER = 1<<23, ///< (lightsource) use hardware shadowmap test
+       SHADERPERMUTATION_SHADOWMAPVSDCT = 1<<24, ///< (lightsource) use virtual shadow depth cube texture for shadowmap indexing
+       SHADERPERMUTATION_SHADOWMAPORTHO = 1<<25, //< (lightsource) use orthographic shadowmap projection
+       SHADERPERMUTATION_DEFERREDLIGHTMAP = 1<<26, ///< (lightmap) read Texture_ScreenDiffuse/Specular textures and add them on top of lightmapping
+       SHADERPERMUTATION_ALPHAKILL = 1<<27, ///< (deferredgeometry) discard pixel if diffuse texture alpha below 0.5
+       SHADERPERMUTATION_REFLECTCUBE = 1<<28, ///< fake reflections using global cubemap (not HDRI light probe)
+       SHADERPERMUTATION_LIMIT = 1<<29, ///< size of permutations array
+       SHADERPERMUTATION_COUNT = 29 ///< size of shaderpermutationinfo array
 }
 shaderpermutation_t;
 
@@ -3274,6 +3380,7 @@ shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] =
        {"#define USESATURATION\n", " saturation"},
        {"#define USEFOGINSIDE\n", " foginside"},
        {"#define USEFOGOUTSIDE\n", " fogoutside"},
+       {"#define USEFOGHEIGHTTEXTURE\n", " fogheighttexture"},
        {"#define USEGAMMARAMPS\n", " gammaramps"},
        {"#define USECUBEFILTER\n", " cubefilter"},
        {"#define USEGLOW\n", " glow"},
@@ -3386,6 +3493,7 @@ typedef struct r_glsl_permutation_s
        int loc_Texture_SecondaryGlow;
        int loc_Texture_Pants;
        int loc_Texture_Shirt;
+       int loc_Texture_FogHeightTexture;
        int loc_Texture_FogMask;
        int loc_Texture_Lightmap;
        int loc_Texture_Deluxemap;
@@ -3612,6 +3720,7 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                p->loc_Texture_SecondaryGlow      = qglGetUniformLocationARB(p->program, "Texture_SecondaryGlow");
                p->loc_Texture_Pants              = qglGetUniformLocationARB(p->program, "Texture_Pants");
                p->loc_Texture_Shirt              = qglGetUniformLocationARB(p->program, "Texture_Shirt");
+               p->loc_Texture_FogHeightTexture   = qglGetUniformLocationARB(p->program, "Texture_FogHeightTexture");
                p->loc_Texture_FogMask            = qglGetUniformLocationARB(p->program, "Texture_FogMask");
                p->loc_Texture_Lightmap           = qglGetUniformLocationARB(p->program, "Texture_Lightmap");
                p->loc_Texture_Deluxemap          = qglGetUniformLocationARB(p->program, "Texture_Deluxemap");
@@ -3694,6 +3803,7 @@ static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode
                if (p->loc_Texture_SecondaryGlow   >= 0) qglUniform1iARB(p->loc_Texture_SecondaryGlow  , GL20TU_SECONDARY_GLOW);
                if (p->loc_Texture_Pants           >= 0) qglUniform1iARB(p->loc_Texture_Pants          , GL20TU_PANTS);
                if (p->loc_Texture_Shirt           >= 0) qglUniform1iARB(p->loc_Texture_Shirt          , GL20TU_SHIRT);
+               if (p->loc_Texture_FogHeightTexture>= 0) qglUniform1iARB(p->loc_Texture_FogHeightTexture, GL20TU_FOGHEIGHTTEXTURE);
                if (p->loc_Texture_FogMask         >= 0) qglUniform1iARB(p->loc_Texture_FogMask        , GL20TU_FOGMASK);
                if (p->loc_Texture_Lightmap        >= 0) qglUniform1iARB(p->loc_Texture_Lightmap       , GL20TU_LIGHTMAP);
                if (p->loc_Texture_Deluxemap       >= 0) qglUniform1iARB(p->loc_Texture_Deluxemap      , GL20TU_DELUXEMAP);
@@ -3810,6 +3920,7 @@ typedef struct r_cg_permutation_s
        CGparameter fp_Texture_SecondaryGlow;
        CGparameter fp_Texture_Pants;
        CGparameter fp_Texture_Shirt;
+       CGparameter fp_Texture_FogHeightTexture;
        CGparameter fp_Texture_FogMask;
        CGparameter fp_Texture_Lightmap;
        CGparameter fp_Texture_Deluxemap;
@@ -4108,6 +4219,7 @@ static void R_CG_CompilePermutation(r_cg_permutation_t *p, unsigned int mode, un
                p->fp_Texture_SecondaryGlow      = cgGetNamedParameter(p->fprogram, "Texture_SecondaryGlow");
                p->fp_Texture_Pants              = cgGetNamedParameter(p->fprogram, "Texture_Pants");
                p->fp_Texture_Shirt              = cgGetNamedParameter(p->fprogram, "Texture_Shirt");
+               p->fp_Texture_FogHeightTexture   = cgGetNamedParameter(p->fprogram, "Texture_FogHeightTexture");
                p->fp_Texture_FogMask            = cgGetNamedParameter(p->fprogram, "Texture_FogMask");
                p->fp_Texture_Lightmap           = cgGetNamedParameter(p->fprogram, "Texture_Lightmap");
                p->fp_Texture_Deluxemap          = cgGetNamedParameter(p->fprogram, "Texture_Deluxemap");
@@ -4467,6 +4579,101 @@ extern rtexture_t *r_shadow_prepassgeometrydepthtexture;
 extern rtexture_t *r_shadow_prepassgeometrynormalmaptexture;
 extern rtexture_t *r_shadow_prepasslightingdiffusetexture;
 extern rtexture_t *r_shadow_prepasslightingspeculartexture;
+static qboolean R_BlendFuncAllowsColormod(int src, int dst)
+{
+       // a blendfunc allows colormod if:
+       // a) it can never keep the destination pixel invariant, or
+       // b) it can keep the destination pixel invariant, and still can do so if colormodded
+       // this is to prevent unintended side effects from colormod
+
+       // in formulas:
+       // IF there is a (s, sa) for which for all (d, da),
+       //   s * src(s, d, sa, da) + d * dst(s, d, sa, da) == d
+       // THEN, for this (s, sa) and all (colormod, d, da):
+       //   s*colormod * src(s*colormod, d, sa, da) + d * dst(s*colormod, d, sa, da) == d
+       // OBVIOUSLY, this means that
+       //   s*colormod * src(s*colormod, d, sa, da) = 0
+       //   dst(s*colormod, d, sa, da)              = 1
+
+       // note: not caring about GL_SRC_ALPHA_SATURATE and following here, these are unused in DP code
+
+       // main condition to leave dst color invariant:
+       //   s * src(s, d, sa, da) + d * dst(s, d, sa, da) == d
+       //   src == GL_ZERO:
+       //     s * 0 + d * dst(s, d, sa, da) == d
+       //       => dst == GL_ONE/GL_SRC_COLOR/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is a problem for GL_SRC_COLOR only
+       //   src == GL_ONE:
+       //     s + d * dst(s, d, sa, da) == d
+       //       => s == 0
+       //       => dst == GL_ONE/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is never problematic for these
+       //   src == GL_SRC_COLOR:
+       //     s*s + d * dst(s, d, sa, da) == d
+       //       => s == 0
+       //       => dst == GL_ONE/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is never problematic for these
+       //   src == GL_ONE_MINUS_SRC_COLOR:
+       //     s*(1-s) + d * dst(s, d, sa, da) == d
+       //       => s == 0 or s == 1
+       //       => dst == GL_ONE/GL_SRC_COLOR/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is a problem for GL_SRC_COLOR only
+       //   src == GL_DST_COLOR
+       //     s*d + d * dst(s, d, sa, da) == d
+       //       => s == 1
+       //       => dst == GL_ZERO/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is always a problem
+       //     or
+       //       => s == 0
+       //       => dst == GL_ONE/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is never problematic for these
+       //       => BUT, we do not know s! We must assume it is problematic
+       //       then... except in GL_ONE case, where we know all invariant
+       //       cases are fine
+       //   src == GL_ONE_MINUS_DST_COLOR
+       //     s*(1-d) + d * dst(s, d, sa, da) == d
+       //       => s == 0 (1-d is impossible to handle for our desired result)
+       //       => dst == GL_ONE/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is never problematic for these
+       //   src == GL_SRC_ALPHA
+       //     s*sa + d * dst(s, d, sa, da) == d
+       //       => s == 0, or sa == 0
+       //       => dst == GL_ONE/GL_SRC_COLOR/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod breaks in the case GL_SRC_COLOR only
+       //   src == GL_ONE_MINUS_SRC_ALPHA
+       //     s*(1-sa) + d * dst(s, d, sa, da) == d
+       //       => s == 0, or sa == 1
+       //       => dst == GL_ONE/GL_SRC_COLOR/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod breaks in the case GL_SRC_COLOR only
+       //   src == GL_DST_ALPHA
+       //     s*da + d * dst(s, d, sa, da) == d
+       //       => s == 0
+       //       => dst == GL_ONE/GL_ONE_MINUS_SRC_COLOR/GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA
+       //       => colormod is never problematic for these
+
+       switch(src)
+       {
+               case GL_ZERO:
+               case GL_ONE_MINUS_SRC_COLOR:
+               case GL_SRC_ALPHA:
+               case GL_ONE_MINUS_SRC_ALPHA:
+                       if(dst == GL_SRC_COLOR)
+                               return false;
+                       return true;
+               case GL_ONE:
+               case GL_SRC_COLOR:
+               case GL_ONE_MINUS_DST_COLOR:
+               case GL_DST_ALPHA:
+               case GL_ONE_MINUS_DST_ALPHA:
+                       return true;
+               case GL_DST_COLOR:
+                       if(dst == GL_ONE)
+                               return true;
+                       return false;
+               default:
+                       return false;
+       }
+}
 void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass)
 {
        // select a permutation of the lighting shader appropriate to this
@@ -4475,14 +4682,22 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
        // fragment shader on features that are not being used
        unsigned int permutation = 0;
        unsigned int mode = 0;
+       qboolean allow_colormod;
+       static float dummy_colormod[3] = {1, 1, 1};
+       float *colormod = rsurface.colormod;
        float m16f[16];
        if (rsurfacepass == RSURFPASS_BACKGROUND)
        {
                // distorted background
                if (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERSHADER)
                        mode = SHADERMODE_WATER;
-               else
+               else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFRACTION)
                        mode = SHADERMODE_REFRACTION;
+               else
+               {
+                       mode = SHADERMODE_GENERIC;
+                       permutation |= SHADERPERMUTATION_DIFFUSE;
+               }
                R_Mesh_TexCoordPointer(0, 2, rsurface.texcoordtexture2f, rsurface.texcoordtexture2f_bufferobject, rsurface.texcoordtexture2f_bufferoffset);
                R_Mesh_TexCoordPointer(1, 3, rsurface.svector3f, rsurface.svector3f_bufferobject, rsurface.svector3f_bufferoffset);
                R_Mesh_TexCoordPointer(2, 3, rsurface.tvector3f, rsurface.tvector3f_bufferobject, rsurface.tvector3f_bufferoffset);
@@ -4491,6 +4706,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest(false);
                GL_BlendFunc(GL_ONE, GL_ZERO);
+               allow_colormod = R_BlendFuncAllowsColormod(GL_ONE, GL_ZERO);
        }
        else if (rsurfacepass == RSURFPASS_DEFERREDGEOMETRY)
        {
@@ -4526,6 +4742,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest(false);
                GL_BlendFunc(GL_ONE, GL_ZERO);
+               allow_colormod = R_BlendFuncAllowsColormod(GL_ONE, GL_ZERO);
        }
        else if (rsurfacepass == RSURFPASS_RTLIGHT)
        {
@@ -4559,7 +4776,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                                permutation |= SHADERPERMUTATION_EXACTSPECULARMATH;
                }
                if (r_refdef.fogenabled)
-                       permutation |= r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE;
+                       permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE);
                if (rsurface.texture->colormapping)
                        permutation |= SHADERPERMUTATION_COLORMAPPING;
                if (r_shadow_usingshadowmaprect || r_shadow_usingshadowmap2d || r_shadow_usingshadowmapcube)
@@ -4602,6 +4819,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0);
                GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
+               allow_colormod = R_BlendFuncAllowsColormod(GL_SRC_ALPHA, GL_ONE);
        }
        else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)
        {
@@ -4626,7 +4844,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer)
                        permutation |= SHADERPERMUTATION_GLOW;
                if (r_refdef.fogenabled)
-                       permutation |= r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE;
+                       permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE);
                if (rsurface.texture->colormapping)
                        permutation |= SHADERPERMUTATION_COLORMAPPING;
                if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))
@@ -4668,6 +4886,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0);
                GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
+               allow_colormod = R_BlendFuncAllowsColormod(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
        }
        else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT_DIRECTIONAL)
        {
@@ -4698,7 +4917,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                                permutation |= SHADERPERMUTATION_EXACTSPECULARMATH;
                }
                if (r_refdef.fogenabled)
-                       permutation |= r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE;
+                       permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE);
                if (rsurface.texture->colormapping)
                        permutation |= SHADERPERMUTATION_COLORMAPPING;
                if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))
@@ -4739,6 +4958,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0);
                GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
+               allow_colormod = R_BlendFuncAllowsColormod(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
        }
        else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT)
        {
@@ -4762,7 +4982,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer)
                        permutation |= SHADERPERMUTATION_GLOW;
                if (r_refdef.fogenabled)
-                       permutation |= r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE;
+                       permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE);
                if (rsurface.texture->colormapping)
                        permutation |= SHADERPERMUTATION_COLORMAPPING;
                if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))
@@ -4803,6 +5023,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                R_Mesh_ColorPointer(NULL, 0, 0);
                GL_AlphaTest((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0);
                GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
+               allow_colormod = R_BlendFuncAllowsColormod(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
        }
        else
        {
@@ -4825,7 +5046,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                if (rsurface.texture->glowtexture && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer)
                        permutation |= SHADERPERMUTATION_GLOW;
                if (r_refdef.fogenabled)
-                       permutation |= r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE;
+                       permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE);
                if (rsurface.texture->colormapping)
                        permutation |= SHADERPERMUTATION_COLORMAPPING;
                if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))
@@ -4918,7 +5139,10 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                }
                GL_AlphaTest((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0);
                GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
+               allow_colormod = R_BlendFuncAllowsColormod(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2);
        }
+       if(!allow_colormod)
+               colormod = dummy_colormod;
        switch(vid.renderpath)
        {
        case RENDERPATH_GL20:
@@ -4929,8 +5153,8 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                        if (r_glsl_permutation->loc_ModelToLight >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);qglUniformMatrix4fvARB(r_glsl_permutation->loc_ModelToLight, 1, false, m16f);}
                        if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3fARB(r_glsl_permutation->loc_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]);
                        if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3fARB(r_glsl_permutation->loc_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]);
-                       if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, rsurface.colormod[0] * ambientscale, rsurface.colormod[1] * ambientscale, rsurface.colormod[2] * ambientscale);
-                       if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Diffuse, rsurface.colormod[0] * diffusescale, rsurface.colormod[1] * diffusescale, rsurface.colormod[2] * diffusescale);
+                       if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale);
+                       if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale);
                        if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale);
        
                        // additive passes are only darkened by fog, not tinted
@@ -4942,24 +5166,24 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                {
                        if (mode == SHADERMODE_FLATCOLOR)
                        {
-                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, rsurface.colormod[0], rsurface.colormod[1], rsurface.colormod[2]);
+                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, colormod[0], colormod[1], colormod[2]);
                        }
                        else if (mode == SHADERMODE_LIGHTDIRECTION)
                        {
-                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * rsurface.colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * rsurface.colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * rsurface.colormod[2]);
-                               if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Diffuse, r_refdef.lightmapintensity * rsurface.colormod[0], r_refdef.lightmapintensity * rsurface.colormod[1], r_refdef.lightmapintensity * rsurface.colormod[2]);
+                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * colormod[2]);
+                               if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]);
                                if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale);
-                               if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Diffuse, rsurface.colormod[0] * r_shadow_deferred_8bitrange.value, rsurface.colormod[1] * r_shadow_deferred_8bitrange.value, rsurface.colormod[2] * r_shadow_deferred_8bitrange.value);
+                               if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * r_shadow_deferred_8bitrange.value, colormod[1] * r_shadow_deferred_8bitrange.value, colormod[2] * r_shadow_deferred_8bitrange.value);
                                if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value);
                                if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3fARB(r_glsl_permutation->loc_LightColor, rsurface.modellight_diffuse[0], rsurface.modellight_diffuse[1], rsurface.modellight_diffuse[2]);
                                if (r_glsl_permutation->loc_LightDir >= 0) qglUniform3fARB(r_glsl_permutation->loc_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]);
                        }
                        else
                        {
-                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, r_refdef.scene.ambient * rsurface.colormod[0], r_refdef.scene.ambient * rsurface.colormod[1], r_refdef.scene.ambient * rsurface.colormod[2]);
+                               if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]);
                                if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]);
                                if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3fARB(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale);
-                               if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Diffuse, rsurface.colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, rsurface.colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, rsurface.colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value);
+                               if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value);
                                if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3fARB(r_glsl_permutation->loc_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value);
                        }
                        // additive passes are only darkened by fog, not tinted
@@ -5025,9 +5249,10 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                if (r_glsl_permutation->loc_Texture_Shirt           >= 0) R_Mesh_TexBind(GL20TU_SHIRT             , rsurface.texture->shirttexture                      );
                if (r_glsl_permutation->loc_Texture_ReflectMask     >= 0) R_Mesh_TexBind(GL20TU_REFLECTMASK       , rsurface.texture->reflectmasktexture                );
                if (r_glsl_permutation->loc_Texture_ReflectCube     >= 0) R_Mesh_TexBind(GL20TU_REFLECTCUBE       , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube);
+               if (r_glsl_permutation->loc_Texture_FogHeightTexture>= 0) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE  , r_texture_fogheighttexture                          );
                if (r_glsl_permutation->loc_Texture_FogMask         >= 0) R_Mesh_TexBind(GL20TU_FOGMASK           , r_texture_fogattenuation                            );
                if (r_glsl_permutation->loc_Texture_Lightmap        >= 0) R_Mesh_TexBind(GL20TU_LIGHTMAP          , r_texture_white                                     );
-               if (r_glsl_permutation->loc_Texture_Deluxemap       >= 0) R_Mesh_TexBind(GL20TU_LIGHTMAP          , r_texture_blanknormalmap                            );
+               if (r_glsl_permutation->loc_Texture_Deluxemap       >= 0) R_Mesh_TexBind(GL20TU_DELUXEMAP         , r_texture_blanknormalmap                            );
                if (r_glsl_permutation->loc_Texture_Attenuation     >= 0) R_Mesh_TexBind(GL20TU_ATTENUATION       , r_shadow_attenuationgradienttexture                 );
                if (r_glsl_permutation->loc_Texture_Refraction      >= 0) R_Mesh_TexBind(GL20TU_REFRACTION        , r_texture_white                                     );
                if (r_glsl_permutation->loc_Texture_Reflection      >= 0) R_Mesh_TexBind(GL20TU_REFLECTION        , r_texture_white                                     );
@@ -5076,8 +5301,8 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                {
                        if (r_cg_permutation->fp_LightPosition) cgGLSetParameter3f(r_cg_permutation->fp_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]);CHECKCGERROR
                        if (r_cg_permutation->fp_LightColor) cgGLSetParameter3f(r_cg_permutation->fp_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]);CHECKCGERROR
-                       if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, rsurface.colormod[0] * ambientscale, rsurface.colormod[1] * ambientscale, rsurface.colormod[2] * ambientscale);CHECKCGERROR
-                       if (r_cg_permutation->fp_Color_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_Color_Diffuse, rsurface.colormod[0] * diffusescale, rsurface.colormod[1] * diffusescale, rsurface.colormod[2] * diffusescale);CHECKCGERROR
+                       if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale);CHECKCGERROR
+                       if (r_cg_permutation->fp_Color_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale);CHECKCGERROR
                        if (r_cg_permutation->fp_Color_Specular) cgGLSetParameter3f(r_cg_permutation->fp_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale);CHECKCGERROR
 
                        // additive passes are only darkened by fog, not tinted
@@ -5088,24 +5313,24 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                {
                        if (mode == SHADERMODE_FLATCOLOR)
                        {
-                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, rsurface.colormod[0], rsurface.colormod[1], rsurface.colormod[2]);CHECKCGERROR
+                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, colormod[0], colormod[1], colormod[2]);CHECKCGERROR
                        }
                        else if (mode == SHADERMODE_LIGHTDIRECTION)
                        {
-                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * rsurface.colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * rsurface.colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * rsurface.colormod[2]);CHECKCGERROR
-                               if (r_cg_permutation->fp_Color_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_Color_Diffuse, r_refdef.lightmapintensity * rsurface.colormod[0], r_refdef.lightmapintensity * rsurface.colormod[1], r_refdef.lightmapintensity * rsurface.colormod[2]);CHECKCGERROR
+                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * colormod[2]);CHECKCGERROR
+                               if (r_cg_permutation->fp_Color_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]);CHECKCGERROR
                                if (r_cg_permutation->fp_Color_Specular) cgGLSetParameter3f(r_cg_permutation->fp_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale);CHECKCGERROR
-                               if (r_cg_permutation->fp_DeferredMod_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Diffuse, rsurface.colormod[0] * r_shadow_deferred_8bitrange.value, rsurface.colormod[1] * r_shadow_deferred_8bitrange.value, rsurface.colormod[2] * r_shadow_deferred_8bitrange.value);CHECKCGERROR
+                               if (r_cg_permutation->fp_DeferredMod_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Diffuse, colormod[0] * r_shadow_deferred_8bitrange.value, colormod[1] * r_shadow_deferred_8bitrange.value, colormod[2] * r_shadow_deferred_8bitrange.value);CHECKCGERROR
                                if (r_cg_permutation->fp_DeferredMod_Specular) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value);CHECKCGERROR
                                if (r_cg_permutation->fp_LightColor) cgGLSetParameter3f(r_cg_permutation->fp_LightColor, rsurface.modellight_diffuse[0], rsurface.modellight_diffuse[1], rsurface.modellight_diffuse[2]);CHECKCGERROR
                                if (r_cg_permutation->fp_LightDir) cgGLSetParameter3f(r_cg_permutation->fp_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]);CHECKCGERROR
                        }
                        else
                        {
-                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, r_refdef.scene.ambient * rsurface.colormod[0], r_refdef.scene.ambient * rsurface.colormod[1], r_refdef.scene.ambient * rsurface.colormod[2]);CHECKCGERROR
+                               if (r_cg_permutation->fp_Color_Ambient) cgGLSetParameter3f(r_cg_permutation->fp_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]);CHECKCGERROR
                                if (r_cg_permutation->fp_Color_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]);CHECKCGERROR
                                if (r_cg_permutation->fp_Color_Specular) cgGLSetParameter3f(r_cg_permutation->fp_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale);CHECKCGERROR
-                               if (r_cg_permutation->fp_DeferredMod_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Diffuse, rsurface.colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, rsurface.colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, rsurface.colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value);CHECKCGERROR
+                               if (r_cg_permutation->fp_DeferredMod_Diffuse) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Diffuse, colormod[0] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[1] * diffusescale * r_shadow_deferred_8bitrange.value, colormod[2] * diffusescale * r_shadow_deferred_8bitrange.value);CHECKCGERROR
                                if (r_cg_permutation->fp_DeferredMod_Specular) cgGLSetParameter3f(r_cg_permutation->fp_DeferredMod_Specular, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value, specularscale * r_shadow_deferred_8bitrange.value);CHECKCGERROR
                        }
                        // additive passes are only darkened by fog, not tinted
@@ -5170,6 +5395,7 @@ void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting,
                if (r_cg_permutation->fp_Texture_Shirt          ) CG_BindTexture(r_cg_permutation->fp_Texture_Shirt          , rsurface.texture->shirttexture                      );CHECKCGERROR
                if (r_cg_permutation->fp_Texture_ReflectMask    ) CG_BindTexture(r_cg_permutation->fp_Texture_ReflectMask    , rsurface.texture->reflectmasktexture                );CHECKCGERROR
                if (r_cg_permutation->fp_Texture_ReflectCube    ) CG_BindTexture(r_cg_permutation->fp_Texture_ReflectCube    , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube);CHECKCGERROR
+               if (r_cg_permutation->fp_Texture_FogHeightTexture) CG_BindTexture(r_cg_permutation->fp_Texture_FogHeightTexture, r_texture_fogheighttexture                         );CHECKCGERROR
                if (r_cg_permutation->fp_Texture_FogMask        ) CG_BindTexture(r_cg_permutation->fp_Texture_FogMask        , r_texture_fogattenuation                            );CHECKCGERROR
                if (r_cg_permutation->fp_Texture_Lightmap       ) CG_BindTexture(r_cg_permutation->fp_Texture_Lightmap       , r_texture_white                                     );CHECKCGERROR
                if (r_cg_permutation->fp_Texture_Deluxemap      ) CG_BindTexture(r_cg_permutation->fp_Texture_Deluxemap      , r_texture_blanknormalmap                            );CHECKCGERROR
@@ -5468,6 +5694,7 @@ skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewid
                skinframe->avgcolor[3] = avgcolor[4] / (255.0 * cnt); \
        }
 
+extern cvar_t gl_picmip;
 skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain)
 {
        int j;
@@ -5481,6 +5708,9 @@ skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboole
        qboolean ddshasalpha = false;
        float ddsavgcolor[4];
        char basename[MAX_QPATH];
+       int miplevel = R_PicmipForFlags(textureflags);
+       int savemiplevel = miplevel;
+       int mymiplevel;
 
        if (cls.state == ca_dedicated)
                return NULL;
@@ -5495,13 +5725,15 @@ skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboole
        Image_StripImageExtension(name, basename, sizeof(basename));
 
        // check for DDS texture file first
-       if (!r_loaddds || !(ddsbase = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s.dds", basename), textureflags, &ddshasalpha, ddsavgcolor)))
+       if (!r_loaddds || !(ddsbase = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s.dds", basename), textureflags, &ddshasalpha, ddsavgcolor, miplevel)))
        {
-               basepixels = loadimagepixelsbgra(name, complain, true, r_texture_convertsRGB_skin.integer);
+               basepixels = loadimagepixelsbgra(name, complain, true, r_texture_convertsRGB_skin.integer, &miplevel);
                if (basepixels == NULL)
                        return NULL;
        }
 
+       // FIXME handle miplevel
+
        if (developer_loading.integer)
                Con_Printf("loading skin \"%s\"\n", name);
 
@@ -5526,14 +5758,14 @@ skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboole
                skinframe->hasalpha = ddshasalpha;
                VectorCopy(ddsavgcolor, skinframe->avgcolor);
                if (r_loadfog && skinframe->hasalpha)
-                       skinframe->fog = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_mask.dds", skinframe->basename), textureflags | TEXF_ALPHA, NULL, NULL);
+                       skinframe->fog = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_mask.dds", skinframe->basename), textureflags | TEXF_ALPHA, NULL, NULL, miplevel);
                //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]);
        }
        else
        {
                basepixels_width = image_width;
                basepixels_height = image_height;
-               skinframe->base = R_LoadTexture2D (r_main_texturepool, skinframe->basename, basepixels_width, basepixels_height, basepixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->base = R_LoadTexture2D (r_main_texturepool, skinframe->basename, basepixels_width, basepixels_height, basepixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL);
                if (textureflags & TEXF_ALPHA)
                {
                        for (j = 3;j < basepixels_width * basepixels_height * 4;j += 4)
@@ -5555,44 +5787,46 @@ skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboole
                                        pixels[j+2] = 255;
                                        pixels[j+3] = basepixels[j+3];
                                }
-                               skinframe->fog = R_LoadTexture2D (r_main_texturepool, va("%s_mask", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+                               skinframe->fog = R_LoadTexture2D (r_main_texturepool, va("%s_mask", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL);
                                Mem_Free(pixels);
                        }
                }
                R_SKINFRAME_LOAD_AVERAGE_COLORS(basepixels_width * basepixels_height, basepixels[4 * pix + comp]);
                //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->base)
-                       R_SaveTextureDDSFile(skinframe->base, va("dds/%s.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->base, va("dds/%s.dds", skinframe->basename), true, skinframe->hasalpha);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->fog)
-                       R_SaveTextureDDSFile(skinframe->fog, va("dds/%s_mask.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->fog, va("dds/%s_mask.dds", skinframe->basename), true, true);
        }
 
        if (r_loaddds)
        {
+               mymiplevel = savemiplevel;
                if (r_loadnormalmap)
-                       skinframe->nmap = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_norm.dds", skinframe->basename), textureflags | TEXF_ALPHA, NULL, NULL);
-               skinframe->glow = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_glow.dds", skinframe->basename), textureflags, NULL, NULL);
+                       skinframe->nmap = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_norm.dds", skinframe->basename), (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), NULL, NULL, mymiplevel);
+               skinframe->glow = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_glow.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel);
                if (r_loadgloss)
-                       skinframe->gloss = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_gloss.dds", skinframe->basename), textureflags, NULL, NULL);
-               skinframe->pants = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_pants.dds", skinframe->basename), textureflags, NULL, NULL);
-               skinframe->shirt = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_shirt.dds", skinframe->basename), textureflags, NULL, NULL);
-               skinframe->reflect = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_reflect.dds", skinframe->basename), textureflags, NULL, NULL);
+                       skinframe->gloss = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_gloss.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel);
+               skinframe->pants = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_pants.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel);
+               skinframe->shirt = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_shirt.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel);
+               skinframe->reflect = R_LoadTextureDDSFile(r_main_texturepool, va("dds/%s_reflect.dds", skinframe->basename), textureflags, NULL, NULL, mymiplevel);
        }
 
        // _norm is the name used by tenebrae and has been adopted as standard
        if (r_loadnormalmap && skinframe->nmap == NULL)
        {
-               if ((pixels = loadimagepixelsbgra(va("%s_norm", skinframe->basename), false, false, false)) != NULL)
+               mymiplevel = savemiplevel;
+               if ((pixels = loadimagepixelsbgra(va("%s_norm", skinframe->basename), false, false, false, &mymiplevel)) != NULL)
                {
-                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | skinframe->textureflags) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                        Mem_Free(pixels);
                        pixels = NULL;
                }
-               else if (r_shadow_bumpscale_bumpmap.value > 0 && (bumppixels = loadimagepixelsbgra(va("%s_bump", skinframe->basename), false, false, false)) != NULL)
+               else if (r_shadow_bumpscale_bumpmap.value > 0 && (bumppixels = loadimagepixelsbgra(va("%s_bump", skinframe->basename), false, false, false, &mymiplevel)) != NULL)
                {
                        pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4);
                        Image_HeightmapToNormalmap_BGRA(bumppixels, pixels, image_width, image_height, false, r_shadow_bumpscale_bumpmap.value);
-                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | skinframe->textureflags) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                        Mem_Free(pixels);
                        Mem_Free(bumppixels);
                }
@@ -5600,55 +5834,60 @@ skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboole
                {
                        pixels = (unsigned char *)Mem_Alloc(tempmempool, basepixels_width * basepixels_height * 4);
                        Image_HeightmapToNormalmap_BGRA(basepixels, pixels, basepixels_width, basepixels_height, false, r_shadow_bumpscale_basetexture.value);
-                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), basepixels_width, basepixels_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | skinframe->textureflags) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+                       skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va("%s_nmap", skinframe->basename), basepixels_width, basepixels_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                        Mem_Free(pixels);
                }
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->nmap)
-                       R_SaveTextureDDSFile(skinframe->nmap, va("dds/%s_norm.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->nmap, va("dds/%s_norm.dds", skinframe->basename), true, true);
        }
 
        // _luma is supported only for tenebrae compatibility
        // _glow is the preferred name
-       if (skinframe->glow == NULL && ((pixels = loadimagepixelsbgra(va("%s_glow",  skinframe->basename), false, false, r_texture_convertsRGB_skin.integer)) || (pixels = loadimagepixelsbgra(va("%s_luma", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer))))
+       mymiplevel = savemiplevel;
+       if (skinframe->glow == NULL && ((pixels = loadimagepixelsbgra(va("%s_glow",  skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel)) || (pixels = loadimagepixelsbgra(va("%s_luma", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel))))
        {
-               skinframe->glow = R_LoadTexture2D (r_main_texturepool, va("%s_glow", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_glow.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->glow = R_LoadTexture2D (r_main_texturepool, va("%s_glow", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_glow.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->glow)
-                       R_SaveTextureDDSFile(skinframe->glow, va("dds/%s_glow.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->glow, va("dds/%s_glow.dds", skinframe->basename), true, true);
                Mem_Free(pixels);pixels = NULL;
        }
 
-       if (skinframe->gloss == NULL && r_loadgloss && (pixels = loadimagepixelsbgra(va("%s_gloss", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer)))
+       mymiplevel = savemiplevel;
+       if (skinframe->gloss == NULL && r_loadgloss && (pixels = loadimagepixelsbgra(va("%s_gloss", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel)))
        {
-               skinframe->gloss = R_LoadTexture2D (r_main_texturepool, va("%s_gloss", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_gloss.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->gloss = R_LoadTexture2D (r_main_texturepool, va("%s_gloss", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_gloss.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->gloss)
-                       R_SaveTextureDDSFile(skinframe->gloss, va("dds/%s_gloss.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->gloss, va("dds/%s_gloss.dds", skinframe->basename), true, true);
                Mem_Free(pixels);
                pixels = NULL;
        }
 
-       if (skinframe->pants == NULL && (pixels = loadimagepixelsbgra(va("%s_pants", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer)))
+       mymiplevel = savemiplevel;
+       if (skinframe->pants == NULL && (pixels = loadimagepixelsbgra(va("%s_pants", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel)))
        {
-               skinframe->pants = R_LoadTexture2D (r_main_texturepool, va("%s_pants", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->pants = R_LoadTexture2D (r_main_texturepool, va("%s_pants", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->pants)
-                       R_SaveTextureDDSFile(skinframe->pants, va("dds/%s_pants.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->pants, va("dds/%s_pants.dds", skinframe->basename), true, false);
                Mem_Free(pixels);
                pixels = NULL;
        }
 
-       if (skinframe->shirt == NULL && (pixels = loadimagepixelsbgra(va("%s_shirt", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer)))
+       mymiplevel = savemiplevel;
+       if (skinframe->shirt == NULL && (pixels = loadimagepixelsbgra(va("%s_shirt", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel)))
        {
-               skinframe->shirt = R_LoadTexture2D (r_main_texturepool, va("%s_shirt", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->shirt = R_LoadTexture2D (r_main_texturepool, va("%s_shirt", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->shirt)
-                       R_SaveTextureDDSFile(skinframe->shirt, va("dds/%s_shirt.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->shirt, va("dds/%s_shirt.dds", skinframe->basename), true, false);
                Mem_Free(pixels);
                pixels = NULL;
        }
 
-       if (skinframe->reflect == NULL && (pixels = loadimagepixelsbgra(va("%s_reflect", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer)))
+       mymiplevel = savemiplevel;
+       if (skinframe->reflect == NULL && (pixels = loadimagepixelsbgra(va("%s_reflect", skinframe->basename), false, false, r_texture_convertsRGB_skin.integer, &mymiplevel)))
        {
-               skinframe->reflect = R_LoadTexture2D (r_main_texturepool, va("%s_reflect", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, skinframe->textureflags & (gl_texturecompression_reflectmask.integer ? ~0 : ~TEXF_COMPRESS), NULL);
+               skinframe->reflect = R_LoadTexture2D (r_main_texturepool, va("%s_reflect", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_reflectmask.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL);
                if (r_savedds && qglGetCompressedTexImageARB && skinframe->reflect)
-                       R_SaveTextureDDSFile(skinframe->reflect, va("dds/%s_reflect.dds", skinframe->basename), true);
+                       R_SaveTextureDDSFile(skinframe->reflect, va("dds/%s_reflect.dds", skinframe->basename), true, true);
                Mem_Free(pixels);
                pixels = NULL;
        }
@@ -5698,10 +5937,10 @@ skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, co
                temp1 = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8);
                temp2 = temp1 + width * height * 4;
                Image_HeightmapToNormalmap_BGRA(skindata, temp2, width, height, false, r_shadow_bumpscale_basetexture.value);
-               skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, skinframe->textureflags | TEXF_ALPHA, NULL);
+               skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL);
                Mem_Free(temp1);
        }
-       skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_BGRA, skinframe->textureflags, NULL);
+       skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_BGRA, textureflags, -1, NULL);
        if (textureflags & TEXF_ALPHA)
        {
                for (i = 3;i < width * height * 4;i += 4)
@@ -5718,7 +5957,7 @@ skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, co
                        memcpy(fogpixels, skindata, width * height * 4);
                        for (i = 0;i < width * height * 4;i += 4)
                                fogpixels[i] = fogpixels[i+1] = fogpixels[i+2] = 255;
-                       skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, fogpixels, TEXTYPE_BGRA, skinframe->textureflags, NULL);
+                       skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, fogpixels, TEXTYPE_BGRA, textureflags, -1, NULL);
                        Mem_Free(fogpixels);
                }
        }
@@ -5821,27 +6060,27 @@ static void R_SkinFrame_GenerateTexturesFromQPixels(skinframe_t *skinframe, qboo
                // use either a custom palette or the quake palette
                Image_Copy8bitBGRA(skindata, temp1, width * height, palette_bgra_complete);
                Image_HeightmapToNormalmap_BGRA(temp1, temp2, width, height, false, r_shadow_bumpscale_basetexture.value);
-               skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, skinframe->textureflags | TEXF_ALPHA, NULL);
+               skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va("%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (skinframe->textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL);
                Mem_Free(temp1);
        }
 
        if (skinframe->qgenerateglow)
        {
                skinframe->qgenerateglow = false;
-               skinframe->glow = R_LoadTexture2D(r_main_texturepool, va("%s_glow", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, palette_bgra_onlyfullbrights); // glow
+               skinframe->glow = R_LoadTexture2D(r_main_texturepool, va("%s_glow", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_onlyfullbrights); // glow
        }
 
        if (colormapped)
        {
                skinframe->qgeneratebase = false;
-               skinframe->base  = R_LoadTexture2D(r_main_texturepool, va("%s_nospecial", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, skinframe->glow ? palette_bgra_nocolormapnofullbrights : palette_bgra_nocolormap);
-               skinframe->pants = R_LoadTexture2D(r_main_texturepool, va("%s_pants", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, palette_bgra_pantsaswhite);
-               skinframe->shirt = R_LoadTexture2D(r_main_texturepool, va("%s_shirt", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, palette_bgra_shirtaswhite);
+               skinframe->base  = R_LoadTexture2D(r_main_texturepool, va("%s_nospecial", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nocolormapnofullbrights : palette_bgra_nocolormap);
+               skinframe->pants = R_LoadTexture2D(r_main_texturepool, va("%s_pants", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_pantsaswhite);
+               skinframe->shirt = R_LoadTexture2D(r_main_texturepool, va("%s_shirt", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_shirtaswhite);
        }
        else
        {
                skinframe->qgeneratemerged = false;
-               skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, skinframe->glow ? palette_bgra_nofullbrights : palette_bgra_complete);
+               skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nofullbrights : palette_bgra_complete);
        }
 
        if (!skinframe->qgeneratemerged && !skinframe->qgeneratebase)
@@ -5883,7 +6122,7 @@ skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, co
        if (developer_loading.integer)
                Con_Printf("loading embedded 8bit image \"%s\"\n", name);
 
-       skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, palette);
+       skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, palette);
        if (textureflags & TEXF_ALPHA)
        {
                for (i = 0;i < width * height;i++)
@@ -5895,7 +6134,7 @@ skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, co
                        }
                }
                if (r_loadfog && skinframe->hasalpha)
-                       skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, skinframe->textureflags, alphapalette);
+                       skinframe->fog = R_LoadTexture2D(r_main_texturepool, va("%s_fog", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, alphapalette);
        }
 
        R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette)[skindata[pix]*4 + comp]);
@@ -5988,7 +6227,7 @@ rtexture_t *R_LoadCubemap(const char *basename)
                        // generate an image name based on the base and and suffix
                        dpsnprintf(name, sizeof(name), "%s%s", basename, suffix[j][i].suffix);
                        // load it
-                       if ((image_buffer = loadimagepixelsbgra(name, false, false, r_texture_convertsRGB_cubemap.integer)))
+                       if ((image_buffer = loadimagepixelsbgra(name, false, false, r_texture_convertsRGB_cubemap.integer, NULL)))
                        {
                                // an image loaded, make sure width and height are equal
                                if (image_width == image_height && (!cubemappixels || image_width == cubemapsize))
@@ -6017,7 +6256,7 @@ rtexture_t *R_LoadCubemap(const char *basename)
                if (developer_loading.integer)
                        Con_Printf("loading cubemap \"%s\"\n", basename);
 
-               cubemaptexture = R_LoadTextureCubeMap(r_main_texturepool, basename, cubemapsize, cubemappixels, TEXTYPE_BGRA, (gl_texturecompression_lightcubemaps.integer ? TEXF_COMPRESS : 0) | TEXF_FORCELINEAR, NULL);
+               cubemaptexture = R_LoadTextureCubeMap(r_main_texturepool, basename, cubemapsize, cubemappixels, TEXTYPE_BGRA, (gl_texturecompression_lightcubemaps.integer ? TEXF_COMPRESS : 0) | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
                Mem_Free(cubemappixels);
        }
        else
@@ -6124,6 +6363,7 @@ void gl_main_start(void)
        r_texture_whitecube = NULL;
        r_texture_normalizationcube = NULL;
        r_texture_fogattenuation = NULL;
+       r_texture_fogheighttexture = NULL;
        r_texture_gammaramps = NULL;
        r_texture_numcubemaps = 0;
 
@@ -6183,6 +6423,7 @@ void gl_main_start(void)
                R_BuildNormalizationCube();
        }
        r_texture_fogattenuation = NULL;
+       r_texture_fogheighttexture = NULL;
        r_texture_gammaramps = NULL;
        //r_texture_fogintensity = NULL;
        memset(&r_bloomstate, 0, sizeof(r_bloomstate));
@@ -6235,6 +6476,7 @@ void gl_main_shutdown(void)
        r_texture_whitecube = NULL;
        r_texture_normalizationcube = NULL;
        r_texture_fogattenuation = NULL;
+       r_texture_fogheighttexture = NULL;
        r_texture_gammaramps = NULL;
        r_texture_numcubemaps = 0;
        //r_texture_fogintensity = NULL;
@@ -6255,7 +6497,6 @@ extern void CL_ParseEntityLump(char *entitystring);
 void gl_main_newmap(void)
 {
        // FIXME: move this code to client
-       int l;
        char *entities, entname[MAX_QPATH];
        if (r_qwskincache)
                Mem_Free(r_qwskincache);
@@ -6263,17 +6504,12 @@ void gl_main_newmap(void)
        r_qwskincache_size = 0;
        if (cl.worldmodel)
        {
-               strlcpy(entname, cl.worldmodel->name, sizeof(entname));
-               l = (int)strlen(entname) - 4;
-               if (l >= 0 && !strcmp(entname + l, ".bsp"))
+               dpsnprintf(entname, sizeof(entname), "%s.ent", cl.worldnamenoextension);
+               if ((entities = (char *)FS_LoadFile(entname, tempmempool, true, NULL)))
                {
-                       memcpy(entname + l, ".ent", 5);
-                       if ((entities = (char *)FS_LoadFile(entname, tempmempool, true, NULL)))
-                       {
-                               CL_ParseEntityLump(entities);
-                               Mem_Free(entities);
-                               return;
-                       }
+                       CL_ParseEntityLump(entities);
+                       Mem_Free(entities);
+                       return;
                }
                if (cl.worldmodel->brush.entities)
                        CL_ParseEntityLump(cl.worldmodel->brush.entities);
@@ -6330,12 +6566,15 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_showdisabledepthtest);
        Cvar_RegisterVariable(&r_drawportals);
        Cvar_RegisterVariable(&r_drawentities);
+       Cvar_RegisterVariable(&r_draw2d);
+       Cvar_RegisterVariable(&r_drawworld);
        Cvar_RegisterVariable(&r_cullentities_trace);
        Cvar_RegisterVariable(&r_cullentities_trace_samples);
        Cvar_RegisterVariable(&r_cullentities_trace_tempentitysamples);
        Cvar_RegisterVariable(&r_cullentities_trace_enlarge);
        Cvar_RegisterVariable(&r_cullentities_trace_delay);
        Cvar_RegisterVariable(&r_drawviewmodel);
+       Cvar_RegisterVariable(&r_drawexteriormodel);
        Cvar_RegisterVariable(&r_speeds);
        Cvar_RegisterVariable(&r_fullbrights);
        Cvar_RegisterVariable(&r_wateralpha);
@@ -6405,7 +6644,7 @@ void GL_Main_Init(void)
        Cvar_RegisterVariable(&r_framedatasize);
        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);
+       R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL);
 
        Cvar_RegisterVariable(&r_track_sprites);
        Cvar_RegisterVariable(&r_track_sprites_flags);
@@ -6754,7 +6993,7 @@ static void R_View_UpdateEntityLighting (void)
        entity_render_t *ent;
        vec3_t tempdiffusenormal, avg;
        vec_t f, fa, fd, fdd;
-       qboolean skipunseen = r_shadows.integer != 1 || R_Shadow_ShadowMappingEnabled();
+       qboolean skipunseen = r_shadows.integer != 1; //|| R_Shadow_ShadowMappingEnabled();
 
        for (i = 0;i < r_refdef.scene.numentities;i++)
        {
@@ -6892,6 +7131,8 @@ static void R_View_UpdateEntityVisible (void)
                :                                                          RENDER_EXTERIORMODEL;
        if (!r_drawviewmodel.integer)
                renderimask |= RENDER_VIEWMODEL;
+       if (!r_drawexteriormodel.integer)
+               renderimask |= RENDER_EXTERIORMODEL;
        if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs)
        {
                // worldmodel can check visibility
@@ -6900,11 +7141,12 @@ static void R_View_UpdateEntityVisible (void)
                {
                        ent = r_refdef.scene.entities[i];
                        if (!(ent->flags & renderimask))
-                       if (!R_CullBox(ent->mins, ent->maxs) || (ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE)))
+                       if (!R_CullBox(ent->mins, ent->maxs) || (ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE)))
                        if ((ent->flags & (RENDER_NODEPTHTEST | RENDER_VIEWMODEL)) || r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, ent->mins, ent->maxs))
                                r_refdef.viewcache.entityvisible[i] = true;
                }
-               if(r_cullentities_trace.integer && r_refdef.scene.worldmodel->brush.TraceLineOfSight)
+               if(r_cullentities_trace.integer && r_refdef.scene.worldmodel->brush.TraceLineOfSight && !r_refdef.view.useclipplane)
+                       // sorry, this check doesn't work for portal/reflection/refraction renders as the view origin is not useful for culling
                {
                        for (i = 0;i < r_refdef.scene.numentities;i++)
                        {
@@ -7297,13 +7539,29 @@ void R_ResetViewRendering3D(void)
        GL_CullFace(r_refdef.view.cullface_back);
 }
 
+/*
+================
+R_RenderView_UpdateViewVectors
+================
+*/
+static void R_RenderView_UpdateViewVectors(void)
+{
+       // break apart the view matrix into vectors for various purposes
+       // it is important that this occurs outside the RenderScene function because that can be called from reflection renders, where the vectors come out wrong
+       // however the r_refdef.view.origin IS updated in RenderScene intentionally - otherwise the sky renders at the wrong origin, etc
+       Matrix4x4_ToVectors(&r_refdef.view.matrix, r_refdef.view.forward, r_refdef.view.left, r_refdef.view.up, r_refdef.view.origin);
+       VectorNegate(r_refdef.view.left, r_refdef.view.right);
+       // make an inverted copy of the view matrix for tracking sprites
+       Matrix4x4_Invert_Simple(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix);
+}
+
 void R_RenderScene(void);
 void R_RenderWaterPlanes(void);
 
 static void R_Water_StartFrame(void)
 {
        int i;
-       int waterwidth, waterheight, texturewidth, textureheight;
+       int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight;
        r_waterstate_waterplane_t *p;
 
        if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d)
@@ -7327,20 +7585,24 @@ static void R_Water_StartFrame(void)
        // calculate desired texture sizes
        // can't use water if the card does not support the texture size
        if (!r_water.integer || r_showsurfaces.integer)
-               texturewidth = textureheight = waterwidth = waterheight = 0;
+               texturewidth = textureheight = waterwidth = waterheight = camerawidth = cameraheight = 0;
        else if (vid.support.arb_texture_non_power_of_two)
        {
                texturewidth = waterwidth;
                textureheight = waterheight;
+               camerawidth = waterwidth;
+               cameraheight = waterheight;
        }
        else
        {
                for (texturewidth   = 1;texturewidth   < waterwidth ;texturewidth   *= 2);
                for (textureheight  = 1;textureheight  < waterheight;textureheight  *= 2);
+               for (camerawidth    = 1;camerawidth   <= waterwidth; camerawidth    *= 2); camerawidth  /= 2;
+               for (cameraheight   = 1;cameraheight  <= waterheight;cameraheight   *= 2); cameraheight /= 2;
        }
 
        // allocate textures as needed
-       if (r_waterstate.texturewidth != texturewidth || r_waterstate.textureheight != textureheight)
+       if (r_waterstate.texturewidth != texturewidth || r_waterstate.textureheight != textureheight || r_waterstate.camerawidth != camerawidth || r_waterstate.cameraheight != cameraheight)
        {
                r_waterstate.maxwaterplanes = MAX_WATERPLANES;
                for (i = 0, p = r_waterstate.waterplanes;i < r_waterstate.maxwaterplanes;i++, p++)
@@ -7351,10 +7613,15 @@ static void R_Water_StartFrame(void)
                        if (p->texture_reflection)
                                R_FreeTexture(p->texture_reflection);
                        p->texture_reflection = NULL;
+                       if (p->texture_camera)
+                               R_FreeTexture(p->texture_camera);
+                       p->texture_camera = NULL;
                }
                memset(&r_waterstate, 0, sizeof(r_waterstate));
                r_waterstate.texturewidth = texturewidth;
                r_waterstate.textureheight = textureheight;
+               r_waterstate.camerawidth = camerawidth;
+               r_waterstate.cameraheight = cameraheight;
        }
 
        if (r_waterstate.texturewidth)
@@ -7384,8 +7651,13 @@ void R_Water_AddWaterPlane(msurface_t *surface)
        vec3_t normal;
        vec3_t center;
        mplane_t plane;
+       int cam_ent;
        r_waterstate_waterplane_t *p;
        texture_t *t = R_GetCurrentTexture(surface->texture);
+       cam_ent = t->camera_entity;
+       if(!(t->currentmaterialflags & MATERIALFLAG_CAMERA))
+               cam_ent = 0;
+
        // just use the first triangle with a valid normal for any decisions
        VectorClear(normal);
        for (triangleindex = 0, e = rsurface.modelelement3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3)
@@ -7415,8 +7687,9 @@ void R_Water_AddWaterPlane(msurface_t *surface)
 
        // find a matching plane if there is one
        for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++)
-               if (fabs(PlaneDiff(vert[0], &p->plane)) < 1 && fabs(PlaneDiff(vert[1], &p->plane)) < 1 && fabs(PlaneDiff(vert[2], &p->plane)) < 1)
-                       break;
+               if(p->camera_entity == t->camera_entity)
+                       if (fabs(PlaneDiff(vert[0], &p->plane)) < 1 && fabs(PlaneDiff(vert[1], &p->plane)) < 1 && fabs(PlaneDiff(vert[2], &p->plane)) < 1)
+                               break;
        if (planeindex >= r_waterstate.maxwaterplanes)
                return; // nothing we can do, out of planes
 
@@ -7429,16 +7702,20 @@ void R_Water_AddWaterPlane(msurface_t *surface)
                // clear materialflags and pvs
                p->materialflags = 0;
                p->pvsvalid = false;
+               p->camera_entity = t->camera_entity;
        }
        // merge this surface's materialflags into the waterplane
        p->materialflags |= t->currentmaterialflags;
-       // merge this surface's PVS into the waterplane
-       VectorMAM(0.5f, surface->mins, 0.5f, surface->maxs, center);
-       if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS
-        && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0)
+       if(!(p->materialflags & MATERIALFLAG_CAMERA))
        {
-               r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid);
-               p->pvsvalid = true;
+               // merge this surface's PVS into the waterplane
+               VectorMAM(0.5f, surface->mins, 0.5f, surface->maxs, center);
+               if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS
+                && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0)
+               {
+                       r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid);
+                       p->pvsvalid = true;
+               }
        }
 }
 
@@ -7448,6 +7725,7 @@ static void R_Water_ProcessPlanes(void)
        r_refdef_view_t myview;
        int planeindex;
        r_waterstate_waterplane_t *p;
+       vec3_t visorigin;
 
        originalview = r_refdef.view;
 
@@ -7457,15 +7735,22 @@ static void R_Water_ProcessPlanes(void)
                if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
                {
                        if (!p->texture_refraction)
-                               p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, NULL);
+                               p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_refraction", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
                        if (!p->texture_refraction)
                                goto error;
                }
+               else if (p->materialflags & MATERIALFLAG_CAMERA)
+               {
+                       if (!p->texture_camera)
+                               p->texture_camera = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_camera", planeindex), r_waterstate.camerawidth, r_waterstate.cameraheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR, -1, NULL);
+                       if (!p->texture_camera)
+                               goto error;
+               }
 
                if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))
                {
                        if (!p->texture_reflection)
-                               p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, NULL);
+                               p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va("waterplane%i_reflection", planeindex), r_waterstate.texturewidth, r_waterstate.textureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
                        if (!p->texture_reflection)
                                goto error;
                }
@@ -7515,9 +7800,20 @@ static void R_Water_ProcessPlanes(void)
                {
                        r_waterstate.renderingrefraction = true;
                        r_refdef.view = myview;
+
                        r_refdef.view.clipplane = p->plane;
                        VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal);
                        r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist;
+
+                       if((p->materialflags & MATERIALFLAG_CAMERA) && p->camera_entity)
+                       {
+                               // we need to perform a matrix transform to render the view... so let's get the transformation matrix
+                               r_waterstate.renderingrefraction = false; // we don't want to hide the player model from these ones
+                               CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin);
+                               R_RenderView_UpdateViewVectors();
+                               r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false);
+                       }
+
                        PlaneClassify(&r_refdef.view.clipplane);
 
                        R_ResetViewRendering3D();
@@ -7528,6 +7824,47 @@ static void R_Water_ProcessPlanes(void)
                        R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
                        r_waterstate.renderingrefraction = false;
                }
+               else if (p->materialflags & MATERIALFLAG_CAMERA)
+               {
+                       r_refdef.view = myview;
+
+                       r_refdef.view.clipplane = p->plane;
+                       VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal);
+                       r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist;
+
+                       r_refdef.view.width = r_waterstate.camerawidth;
+                       r_refdef.view.height = r_waterstate.cameraheight;
+                       r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0);
+                       r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0);
+
+                       if(p->camera_entity)
+                       {
+                               // we need to perform a matrix transform to render the view... so let's get the transformation matrix
+                               CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin);
+                       }
+
+                       // reverse the cullface settings for this render
+                       r_refdef.view.cullface_front = GL_FRONT;
+                       r_refdef.view.cullface_back = GL_BACK;
+                       // also reverse the view matrix
+                       Matrix4x4_ConcatScale3(&r_refdef.view.matrix, 1, 1, -1); // this serves to invert texcoords in the result, as the copied texture is mapped the wrong way round
+                       R_RenderView_UpdateViewVectors();
+                       if(p->camera_entity)
+                               r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false);
+                       
+                       // camera needs no clipplane
+                       r_refdef.view.useclipplane = false;
+
+                       PlaneClassify(&r_refdef.view.clipplane);
+
+                       R_ResetViewRendering3D();
+                       R_ClearScreen(r_refdef.fogenabled);
+                       R_View_Update();
+                       R_RenderScene();
+
+                       R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+                       r_waterstate.renderingrefraction = false;
+               }
 
        }
        r_waterstate.renderingscene = false;
@@ -7604,7 +7941,7 @@ void R_Bloom_StartFrame(void)
                r_bloomstate.screentexturewidth = screentexturewidth;
                r_bloomstate.screentextureheight = screentextureheight;
                if (r_bloomstate.screentexturewidth && r_bloomstate.screentextureheight)
-                       r_bloomstate.texture_screen = R_LoadTexture2D(r_main_texturepool, "screen", r_bloomstate.screentexturewidth, r_bloomstate.screentextureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCENEAREST | TEXF_CLAMP, NULL);
+                       r_bloomstate.texture_screen = R_LoadTexture2D(r_main_texturepool, "screen", r_bloomstate.screentexturewidth, r_bloomstate.screentextureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCENEAREST | TEXF_CLAMP, -1, NULL);
        }
        if (r_bloomstate.bloomtexturewidth != bloomtexturewidth || r_bloomstate.bloomtextureheight != bloomtextureheight)
        {
@@ -7614,7 +7951,7 @@ void R_Bloom_StartFrame(void)
                r_bloomstate.bloomtexturewidth = bloomtexturewidth;
                r_bloomstate.bloomtextureheight = bloomtextureheight;
                if (r_bloomstate.bloomtexturewidth && r_bloomstate.bloomtextureheight)
-                       r_bloomstate.texture_bloom = R_LoadTexture2D(r_main_texturepool, "bloom", r_bloomstate.bloomtexturewidth, r_bloomstate.bloomtextureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, NULL);
+                       r_bloomstate.texture_bloom = R_LoadTexture2D(r_main_texturepool, "bloom", r_bloomstate.bloomtexturewidth, r_bloomstate.bloomtextureheight, NULL, TEXTYPE_COLORBUFFER, TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL);
        }
 
        // when doing a reduced render (HDR) we want to use a smaller area
@@ -8118,10 +8455,14 @@ void R_UpdateVariables(void)
                r_refdef.fogrange = bound(r_refdef.fog_start, r_refdef.fogrange, r_refdef.fog_end);
                r_refdef.fograngerecip = 1.0f / r_refdef.fogrange;
                r_refdef.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * r_refdef.fograngerecip;
+               if (strcmp(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename))
+                       R_BuildFogHeightTexture();
                // fog color was already set
                // update the fog texture
                if (r_refdef.fogmasktable_start != r_refdef.fog_start || r_refdef.fogmasktable_alpha != r_refdef.fog_alpha || r_refdef.fogmasktable_density != r_refdef.fog_density || r_refdef.fogmasktable_range != r_refdef.fogrange)
                        R_BuildFogTexture();
+               r_refdef.fog_height_texcoordscale = 1.0f / max(0.125f, r_refdef.fog_fadedepth);
+               r_refdef.fog_height_tablescale = r_refdef.fog_height_tablesize * r_refdef.fog_height_texcoordscale;
        }
        else
                r_refdef.fogenabled = false;
@@ -8156,7 +8497,7 @@ void R_UpdateVariables(void)
                                }
                                else
                                {
-                                       r_texture_gammaramps = R_LoadTexture2D(r_main_texturepool, "gammaramps", RAMPWIDTH, 1, &rampbgr[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT | TEXF_ALLOWUPDATES, NULL);
+                                       r_texture_gammaramps = R_LoadTexture2D(r_main_texturepool, "gammaramps", RAMPWIDTH, 1, &rampbgr[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL);
                                }
                        }
                }
@@ -8238,18 +8579,12 @@ void R_RenderView(void)
                return;
        }
 
-       if (!r_refdef.scene.entities || r_refdef.view.width * r_refdef.view.height == 0 || !r_renderview.integer/* || !r_refdef.scene.worldmodel*/)
+       if (!r_refdef.scene.entities || r_refdef.view.width * r_refdef.view.height == 0 || !r_renderview.integer || cl_videoplaying/* || !r_refdef.scene.worldmodel*/)
                return; //Host_Error ("R_RenderView: NULL worldmodel");
 
        r_refdef.view.colorscale = r_hdr_scenebrightness.value;
 
-       // break apart the view matrix into vectors for various purposes
-       // it is important that this occurs outside the RenderScene function because that can be called from reflection renders, where the vectors come out wrong
-       // however the r_refdef.view.origin IS updated in RenderScene intentionally - otherwise the sky renders at the wrong origin, etc
-       Matrix4x4_ToVectors(&r_refdef.view.matrix, r_refdef.view.forward, r_refdef.view.left, r_refdef.view.up, r_refdef.view.origin);
-       VectorNegate(r_refdef.view.left, r_refdef.view.right);
-       // make an inverted copy of the view matrix for tracking sprites
-       Matrix4x4_Invert_Simple(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix);
+       R_RenderView_UpdateViewVectors();
 
        R_Shadow_UpdateWorldLightSelection();
 
@@ -8416,7 +8751,7 @@ void R_RenderScene(void)
                        R_TimeReport("modeldepth");
        }
 
-       if (r_shadows.integer > 0 && shadowmapping && r_refdef.lightmapintensity > 0)
+       if (r_shadows.integer >= 2 && shadowmapping && r_refdef.lightmapintensity > 0)
        {
                R_DrawModelShadowMaps();
                R_ResetViewRendering3D();
@@ -8444,7 +8779,7 @@ void R_RenderScene(void)
        if (r_refdef.scene.extraupdate)
                S_ExtraUpdate ();
 
-       if (r_shadows.integer > 0 && !shadowmapping && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
+       if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
        {
                R_DrawModelShadows();
                R_ResetViewRendering3D();
@@ -8464,7 +8799,7 @@ void R_RenderScene(void)
        if (r_refdef.scene.extraupdate)
                S_ExtraUpdate ();
 
-       if (r_shadows.integer > 0 && !shadowmapping && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
+       if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0)
        {
                R_DrawModelShadows();
                R_ResetViewRendering3D();
@@ -8943,12 +9278,22 @@ static void R_Texture_AddLayer(texture_t *t, qboolean depthmask, int blendfunc1,
        layer->color[3] = a;
 }
 
+static qboolean R_TestQ3WaveFunc(q3wavefunc_t func, const float *parms)
+{
+       if(parms[0] == 0 && parms[1] == 0)
+               return false;
+       if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set!
+               if(rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT)] == 0)
+                       return false;
+       return true;
+}
+
 static float R_EvaluateQ3WaveFunc(q3wavefunc_t func, const float *parms)
 {
        double index, f;
        index = parms[2] + r_refdef.scene.time * parms[3];
        index -= floor(index);
-       switch (func)
+       switch (func & ((1 << Q3WAVEFUNC_USER_SHIFT) - 1))
        {
        default:
        case Q3WAVEFUNC_NONE:
@@ -8973,7 +9318,10 @@ static float R_EvaluateQ3WaveFunc(q3wavefunc_t func, const float *parms)
                        f = -(1 - f);
                break;
        }
-       return (float)(parms[0] + parms[1] * f);
+       f = parms[0] + parms[1] * f;
+       if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set!
+               f *= rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT)];
+       return (float) f;
 }
 
 void R_tcMod_ApplyToMatrix(matrix4x4_t *texmatrix, q3shaderinfo_layer_tcmod_t *tcmod, int currentmaterialflags)
@@ -9074,6 +9422,11 @@ 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)
+               t->camera_entity = ent->entitynumber;
+       else
+               t->camera_entity = 0;
+
        // switch to an alternate material if this is a q1bsp animated material
        {
                texture_t *texture = t;
@@ -9130,7 +9483,7 @@ texture_t *R_GetCurrentTexture(texture_t *t)
        if(t->basematerialflags & MATERIALFLAG_WATERSHADER && r_waterstate.enabled && !r_refdef.view.isoverlay)
                t->currentalpha *= t->r_water_wateralpha;
        if(!r_waterstate.enabled || r_refdef.view.isoverlay)
-               t->currentmaterialflags &= ~(MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION);
+               t->currentmaterialflags &= ~(MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA);
        if (!(rsurface.ent_flags & RENDER_LIGHT))
                t->currentmaterialflags |= MATERIALFLAG_FULLBRIGHT;
        else if (rsurface.modeltexcoordlightmap2f == NULL && !(t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT))
@@ -9153,11 +9506,11 @@ texture_t *R_GetCurrentTexture(texture_t *t)
                t->currentmaterialflags |= MATERIALFLAG_VERTEXTEXTUREBLEND;
        if (t->currentmaterialflags & MATERIALFLAG_BLENDED)
        {
-               if (t->currentmaterialflags & (MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER))
+               if (t->currentmaterialflags & (MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA))
                        t->currentmaterialflags &= ~MATERIALFLAG_BLENDED;
        }
        else
-               t->currentmaterialflags &= ~(MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER);
+               t->currentmaterialflags &= ~(MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA);
        if ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST)) == MATERIALFLAG_BLENDED && r_transparentdepthmasking.integer && !(t->basematerialflags & MATERIALFLAG_BLENDED))
                t->currentmaterialflags |= MATERIALFLAG_TRANSDEPTH;
 
@@ -9282,6 +9635,9 @@ texture_t *R_GetCurrentTexture(texture_t *t)
                        blendfunc1 = GL_ONE;
                        blendfunc2 = GL_ZERO;
                }
+               // don't colormod evilblend textures
+               if(!R_BlendFuncAllowsColormod(blendfunc1, blendfunc2))
+                       VectorSet(t->lightmapcolor, 1, 1, 1);
                depthmask = !(t->currentmaterialflags & MATERIALFLAG_BLENDED);
                if (t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)
                {
@@ -9378,6 +9734,7 @@ void RSurf_ActiveWorldEntity(void)
        //      return;
        rsurface.entity = r_refdef.scene.worldentity;
        rsurface.skeleton = NULL;
+       memset(rsurface.userwavefunc_param, 0, sizeof(rsurface.userwavefunc_param));
        rsurface.ent_skinnum = 0;
        rsurface.ent_qwskin = -1;
        rsurface.ent_shadertime = 0;
@@ -9460,6 +9817,7 @@ void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, q
        //      return;
        rsurface.entity = (entity_render_t *)ent;
        rsurface.skeleton = ent->skeleton;
+       memcpy(rsurface.userwavefunc_param, ent->userwavefunc_param, sizeof(rsurface.userwavefunc_param));
        rsurface.ent_skinnum = ent->skinnum;
        rsurface.ent_qwskin = (ent->entitynumber <= cl.maxclients && ent->entitynumber >= 1 && cls.protocol == PROTOCOL_QUAKEWORLD && cl.scores[ent->entitynumber - 1].qw_skin[0] && !strcmp(ent->model->name, "progs/player.mdl")) ? (ent->entitynumber - 1) : -1;
        rsurface.ent_shadertime = ent->shadertime;
@@ -10008,6 +10366,8 @@ void RSurf_PrepareVerticesForBatch(qboolean generatenormals, qboolean generateta
                        waveparms[1] = deform->waveparms[1];
                        waveparms[2] = deform->waveparms[2];
                        waveparms[3] = deform->waveparms[3];
+                       if(!R_TestQ3WaveFunc(deform->wavefunc, waveparms))
+                               break; // if wavefunc is a nop, don't make a dynamic vertex array
                        // this is how a divisor of vertex influence on deformation
                        animpos = deform->parms[0] ? 1.0f / deform->parms[0] : 100.0f;
                        scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms);
@@ -10048,6 +10408,8 @@ void RSurf_PrepareVerticesForBatch(qboolean generatenormals, qboolean generateta
                        break;
                case Q3DEFORM_MOVE:
                        // deform vertex array
+                       if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms))
+                               break; // if wavefunc is a nop, don't make a dynamic vertex array
                        scale = R_EvaluateQ3WaveFunc(deform->wavefunc, deform->waveparms);
                        VectorScale(deform->parms, scale, waveparms);
                        for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++)
@@ -10259,6 +10621,8 @@ static void RSurf_BindReflectionForSurface(const msurface_t *surface)
        bestp = NULL;
        for (planeindex = 0, p = r_waterstate.waterplanes;planeindex < r_waterstate.numwaterplanes;planeindex++, p++)
        {
+               if(p->camera_entity != rsurface.texture->camera_entity)
+                       continue;
                d = 0;
                for (vertexindex = 0, v = rsurface.modelvertex3f + surface->num_firstvertex * 3;vertexindex < surface->num_vertices;vertexindex++, v += 3)
                {
@@ -10275,12 +10639,14 @@ static void RSurf_BindReflectionForSurface(const msurface_t *surface)
        {
        case RENDERPATH_CGGL:
 #ifdef SUPPORTCG
-               if (r_cg_permutation->fp_Texture_Refraction) CG_BindTexture(r_cg_permutation->fp_Texture_Refraction, bestp ? bestp->texture_refraction : r_texture_black);CHECKCGERROR
-               if (r_cg_permutation->fp_Texture_Reflection) CG_BindTexture(r_cg_permutation->fp_Texture_Reflection, bestp ? bestp->texture_reflection : r_texture_black);CHECKCGERROR
+               if (r_cg_permutation->fp_Texture_Refraction) {CG_BindTexture(r_cg_permutation->fp_Texture_Refraction, bestp ? bestp->texture_refraction : r_texture_black);CHECKCGERROR}
+               else if (r_cg_permutation->fp_Texture_First) {CG_BindTexture(r_cg_permutation->fp_Texture_First, bestp ? bestp->texture_camera : r_texture_black);CHECKCGERROR}
+               if (r_cg_permutation->fp_Texture_Reflection) {CG_BindTexture(r_cg_permutation->fp_Texture_Reflection, bestp ? bestp->texture_reflection : r_texture_black);CHECKCGERROR}
 #endif
                break;
        case RENDERPATH_GL20:
                if (r_glsl_permutation->loc_Texture_Refraction >= 0) R_Mesh_TexBind(GL20TU_REFRACTION, bestp ? bestp->texture_refraction : r_texture_black);
+               else if (r_glsl_permutation->loc_Texture_First >= 0) R_Mesh_TexBind(GL20TU_FIRST, bestp ? bestp->texture_camera : r_texture_black);
                if (r_glsl_permutation->loc_Texture_Reflection >= 0) R_Mesh_TexBind(GL20TU_REFLECTION, bestp ? bestp->texture_reflection : r_texture_black);
                break;
        case RENDERPATH_GL13:
@@ -10788,7 +11154,7 @@ extern rtexture_t *r_shadow_prepasslightingdiffusetexture;
 extern rtexture_t *r_shadow_prepasslightingspeculartexture;
 static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass)
 {
-       if (r_waterstate.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION)))
+       if (r_waterstate.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)))
                return;
        RSurf_PrepareVerticesForBatch(true, true, texturenumsurfaces, texturesurfacelist);
        if (prepass)
@@ -10798,7 +11164,7 @@ static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, const msurface
                R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_DEFERREDGEOMETRY);
                RSurf_DrawBatch_Simple(texturenumsurfaces, texturesurfacelist);
        }
-       else if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) && !r_waterstate.renderingscene)
+       else if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA)) && !r_waterstate.renderingscene)
        {
                // render water or distortion background, then blend surface on top
                GL_DepthMask(true);
@@ -11701,13 +12067,92 @@ static void R_DecalSystem_SpawnTriangle(decalsystem_t *decalsystem, const float
 extern cvar_t cl_decals_bias;
 extern cvar_t cl_decals_models;
 extern cvar_t cl_decals_newsystem_intensitymultiplier;
+// baseparms, parms, temps
+static void R_DecalSystem_SplatTriangle(decalsystem_t *decalsystem, float r, float g, float b, float a, float s1, float t1, float s2, float t2, int decalsequence, qboolean dynamic, float (*planes)[4], matrix4x4_t *projection, int triangleindex, int surfaceindex)
+{
+       int cornerindex;
+       int index;
+       float v[9][3];
+       const float *vertex3f;
+       int numpoints;
+       float points[2][9][3];
+       float temp[3];
+       float tc[9][2];
+       float f;
+       float c[9][4];
+       const int *e;
+
+       e = rsurface.modelelement3i + 3*triangleindex;
+
+       vertex3f = rsurface.modelvertex3f;
+
+       for (cornerindex = 0;cornerindex < 3;cornerindex++)
+       {
+               index = 3*e[cornerindex];
+               VectorCopy(vertex3f + index, v[cornerindex]);
+       }
+       // cull backfaces
+       //TriangleNormal(v[0], v[1], v[2], normal);
+       //if (DotProduct(normal, localnormal) < 0.0f)
+       //      continue;
+       // clip by each of the box planes formed from the projection matrix
+       // if anything survives, we emit the decal
+       numpoints = PolygonF_Clip(3        , v[0]        , planes[0][0], planes[0][1], planes[0][2], planes[0][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
+       if (numpoints < 3)
+               return;
+       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[1][0], planes[1][1], planes[1][2], planes[1][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]);
+       if (numpoints < 3)
+               return;
+       numpoints = PolygonF_Clip(numpoints, points[0][0], planes[2][0], planes[2][1], planes[2][2], planes[2][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
+       if (numpoints < 3)
+               return;
+       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[3][0], planes[3][1], planes[3][2], planes[3][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]);
+       if (numpoints < 3)
+               return;
+       numpoints = PolygonF_Clip(numpoints, points[0][0], planes[4][0], planes[4][1], planes[4][2], planes[4][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
+       if (numpoints < 3)
+               return;
+       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[5][0], planes[5][1], planes[5][2], planes[5][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), v[0]);
+       if (numpoints < 3)
+               return;
+       // some part of the triangle survived, so we have to accept it...
+       if (dynamic)
+       {
+               // dynamic always uses the original triangle
+               numpoints = 3;
+               for (cornerindex = 0;cornerindex < 3;cornerindex++)
+               {
+                       index = 3*e[cornerindex];
+                       VectorCopy(vertex3f + index, v[cornerindex]);
+               }
+       }
+       for (cornerindex = 0;cornerindex < numpoints;cornerindex++)
+       {
+               // convert vertex positions to texcoords
+               Matrix4x4_Transform(projection, v[cornerindex], temp);
+               tc[cornerindex][0] = (temp[1]+1.0f)*0.5f * (s2-s1) + s1;
+               tc[cornerindex][1] = (temp[2]+1.0f)*0.5f * (t2-t1) + t1;
+               // calculate distance fade from the projection origin
+               f = a * (1.0f-fabs(temp[0])) * cl_decals_newsystem_intensitymultiplier.value;
+               f = bound(0.0f, f, 1.0f);
+               c[cornerindex][0] = r * f;
+               c[cornerindex][1] = g * f;
+               c[cornerindex][2] = b * f;
+               c[cornerindex][3] = 1.0f;
+               //VectorMA(v[cornerindex], cl_decals_bias.value, localnormal, v[cornerindex]);
+       }
+       if (dynamic)
+               R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[1], v[2], tc[0], tc[1], tc[2], c[0], c[1], c[2], triangleindex, surfaceindex, decalsequence);
+       else
+               for (cornerindex = 0;cornerindex < numpoints-2;cornerindex++)
+                       R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[cornerindex+1], v[cornerindex+2], tc[0], tc[cornerindex+1], tc[cornerindex+2], c[0], c[cornerindex+1], c[cornerindex+2], -1, surfaceindex, decalsequence);
+}
 static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, int decalsequence)
 {
        matrix4x4_t projection;
        decalsystem_t *decalsystem;
        qboolean dynamic;
        dp_model_t *model;
-       const float *vertex3f;
        const msurface_t *surface;
        const msurface_t *surfaces;
        const int *surfacelist;
@@ -11717,24 +12162,18 @@ static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldor
        int surfacelistindex;
        int surfaceindex;
        int triangleindex;
-       int cornerindex;
-       int index;
-       int numpoints;
-       const int *e;
        float localorigin[3];
        float localnormal[3];
        float localmins[3];
        float localmaxs[3];
        float localsize;
-       float v[9][3];
-       float tc[9][2];
-       float c[9][4];
        //float normal[3];
        float planes[6][4];
-       float f;
-       float points[2][9][3];
        float angles[3];
-       float temp[3];
+       bih_t *bih;
+       int bih_triangles_count;
+       int bih_triangles[256];
+       int bih_surfaces[256];
 
        decalsystem = &ent->decalsystem;
        model = ent->model;
@@ -11813,86 +12252,57 @@ static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldor
 #endif
 
        dynamic = model->surfmesh.isanimated;
-       vertex3f = rsurface.modelvertex3f;
        numsurfacelist = model->nummodelsurfaces;
        surfacelist = model->sortedmodelsurfaces;
        surfaces = model->data_surfaces;
-       for (surfacelistindex = 0;surfacelistindex < numsurfacelist;surfacelistindex++)
+
+       bih = NULL;
+       bih_triangles_count = -1;
+       if(!dynamic)
        {
-               surfaceindex = surfacelist[surfacelistindex];
-               surface = surfaces + surfaceindex;
-               // check cull box first because it rejects more than any other check
-               if (!dynamic && !BoxesOverlap(surface->mins, surface->maxs, localmins, localmaxs))
-                       continue;
-               // skip transparent surfaces
-               texture = surface->texture;
-               if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
-                       continue;
-               if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS)
-                       continue;
-               numtriangles = surface->num_triangles;
-               for (triangleindex = 0, e = model->surfmesh.data_element3i + 3*surface->num_firsttriangle;triangleindex < numtriangles;triangleindex++, e += 3)
+               if(model->render_bih.numleafs)
+                       bih = &model->render_bih;
+               else if(model->collision_bih.numleafs)
+                       bih = &model->collision_bih;
+       }
+       if(bih)
+               bih_triangles_count = BIH_GetTriangleListForBox(bih, sizeof(bih_triangles) / sizeof(*bih_triangles), bih_triangles, bih_surfaces, localmins, localmaxs);
+       if(bih_triangles_count == 0)
+               return;
+       if(bih_triangles_count > (int) (sizeof(bih_triangles) / sizeof(*bih_triangles))) // hit too many, likely bad anyway
+               return;
+       if(bih_triangles_count > 0)
+       {
+               for (triangleindex = 0; triangleindex < bih_triangles_count; ++triangleindex)
                {
-                       for (cornerindex = 0;cornerindex < 3;cornerindex++)
-                       {
-                               index = 3*e[cornerindex];
-                               VectorCopy(vertex3f + index, v[cornerindex]);
-                       }
-                       // cull backfaces
-                       //TriangleNormal(v[0], v[1], v[2], normal);
-                       //if (DotProduct(normal, localnormal) < 0.0f)
-                       //      continue;
-                       // clip by each of the box planes formed from the projection matrix
-                       // if anything survives, we emit the decal
-                       numpoints = PolygonF_Clip(3        , v[0]        , planes[0][0], planes[0][1], planes[0][2], planes[0][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
-                       if (numpoints < 3)
-                               continue;
-                       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[1][0], planes[1][1], planes[1][2], planes[1][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]);
-                       if (numpoints < 3)
+                       surfaceindex = bih_surfaces[triangleindex];
+                       surface = surfaces + surfaceindex;
+                       texture = surface->texture;
+                       if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
                                continue;
-                       numpoints = PolygonF_Clip(numpoints, points[0][0], planes[2][0], planes[2][1], planes[2][2], planes[2][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
-                       if (numpoints < 3)
+                       if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS)
                                continue;
-                       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[3][0], planes[3][1], planes[3][2], planes[3][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]);
-                       if (numpoints < 3)
+                       R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, bih_triangles[triangleindex], surfaceindex);
+               }
+       }
+       else
+       {
+               for (surfacelistindex = 0;surfacelistindex < numsurfacelist;surfacelistindex++)
+               {
+                       surfaceindex = surfacelist[surfacelistindex];
+                       surface = surfaces + surfaceindex;
+                       // check cull box first because it rejects more than any other check
+                       if (!dynamic && !BoxesOverlap(surface->mins, surface->maxs, localmins, localmaxs))
                                continue;
-                       numpoints = PolygonF_Clip(numpoints, points[0][0], planes[4][0], planes[4][1], planes[4][2], planes[4][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]);
-                       if (numpoints < 3)
+                       // skip transparent surfaces
+                       texture = surface->texture;
+                       if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION))
                                continue;
-                       numpoints = PolygonF_Clip(numpoints, points[1][0], planes[5][0], planes[5][1], planes[5][2], planes[5][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), v[0]);
-                       if (numpoints < 3)
+                       if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS)
                                continue;
-                       // some part of the triangle survived, so we have to accept it...
-                       if (dynamic)
-                       {
-                               // dynamic always uses the original triangle
-                               numpoints = 3;
-                               for (cornerindex = 0;cornerindex < 3;cornerindex++)
-                               {
-                                       index = 3*e[cornerindex];
-                                       VectorCopy(vertex3f + index, v[cornerindex]);
-                               }
-                       }
-                       for (cornerindex = 0;cornerindex < numpoints;cornerindex++)
-                       {
-                               // convert vertex positions to texcoords
-                               Matrix4x4_Transform(&projection, v[cornerindex], temp);
-                               tc[cornerindex][0] = (temp[1]+1.0f)*0.5f * (s2-s1) + s1;
-                               tc[cornerindex][1] = (temp[2]+1.0f)*0.5f * (t2-t1) + t1;
-                               // calculate distance fade from the projection origin
-                               f = a * (1.0f-fabs(temp[0])) * cl_decals_newsystem_intensitymultiplier.value;
-                               f = bound(0.0f, f, 1.0f);
-                               c[cornerindex][0] = r * f;
-                               c[cornerindex][1] = g * f;
-                               c[cornerindex][2] = b * f;
-                               c[cornerindex][3] = 1.0f;
-                               //VectorMA(v[cornerindex], cl_decals_bias.value, localnormal, v[cornerindex]);
-                       }
-                       if (dynamic)
-                               R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[1], v[2], tc[0], tc[1], tc[2], c[0], c[1], c[2], triangleindex+surface->num_firsttriangle, surfaceindex, decalsequence);
-                       else
-                               for (cornerindex = 0;cornerindex < numpoints-2;cornerindex++)
-                                       R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[cornerindex+1], v[cornerindex+2], tc[0], tc[cornerindex+1], tc[cornerindex+2], c[0], c[cornerindex+1], c[cornerindex+2], -1, surfaceindex, decalsequence);
+                       numtriangles = surface->num_triangles;
+                       for (triangleindex = 0; triangleindex < numtriangles; triangleindex++)
+                               R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, triangleindex + surface->num_firsttriangle, surfaceindex);
                }
        }
 }