From 9c0b5b3f72496c071a4c4740dc21d1b0c5d5eae0 Mon Sep 17 00:00:00 2001
From: havoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Wed, 21 Feb 2007 11:55:37 +0000
Subject: [PATCH] modified surface renderer batching to batch all lightmapped
 surfaces with the same texture together, rather than a separate batch for
 each lightmap texture, this means that the lightmap switching overhead is
 lower than before, no apparent improvement in benchmarks though

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6883 d7cf8633-e32d-0410-b094-e92efae38249
---
 gl_rmain.c | 226 +++++++++++++++++++++++++++++++++++------------------
 render.h   |   2 +-
 todo       |   1 +
 3 files changed, 151 insertions(+), 78 deletions(-)

diff --git a/gl_rmain.c b/gl_rmain.c
index f6475aa4..3404cd0d 100644
--- a/gl_rmain.c
+++ b/gl_rmain.c
@@ -916,10 +916,10 @@ int R_SetupSurfaceShader(const vec3_t lightcolorbase, qboolean modellighting)
 		// lightmapped wall
 		shaderfilename = "glsl/default.glsl";
 		permutation = SHADERPERMUTATION_USES_VERTEXSHADER | SHADERPERMUTATION_USES_FRAGMENTSHADER;
-		if (r_glsl_deluxemapping.integer >= 1 && rsurface_lightmaptexture && r_refdef.worldmodel && r_refdef.worldmodel->brushq3.deluxemapping)
+		if (r_glsl_deluxemapping.integer >= 1 && rsurface_uselightmaptexture && r_refdef.worldmodel && r_refdef.worldmodel->brushq3.deluxemapping)
 		{
 			// deluxemapping (light direction texture)
-			if (rsurface_lightmaptexture && r_refdef.worldmodel && r_refdef.worldmodel->brushq3.deluxemapping && r_refdef.worldmodel->brushq3.deluxemapping_modelspace)
+			if (rsurface_uselightmaptexture && r_refdef.worldmodel && r_refdef.worldmodel->brushq3.deluxemapping && r_refdef.worldmodel->brushq3.deluxemapping_modelspace)
 				permutation |= SHADERPERMUTATION_MODE_LIGHTDIRECTIONMAP_MODELSPACE;
 			else
 				permutation |= SHADERPERMUTATION_MODE_LIGHTDIRECTIONMAP_TANGENTSPACE;
@@ -2814,7 +2814,7 @@ qboolean rsurface_generatedvertex;
 const entity_render_t *rsurface_entity;
 const model_t *rsurface_model;
 texture_t *rsurface_texture;
-rtexture_t *rsurface_lightmaptexture;
+qboolean rsurface_uselightmaptexture;
 rsurfmode_t rsurface_mode;
 texture_t *rsurface_glsl_texture;
 qboolean rsurface_glsl_uselightmap;
@@ -2828,7 +2828,7 @@ void RSurf_CleanUp(void)
 	}
 	GL_AlphaTest(false);
 	rsurface_mode = RSURFMODE_NONE;
-	rsurface_lightmaptexture = NULL;
+	rsurface_uselightmaptexture = false;
 	rsurface_texture = NULL;
 	rsurface_glsl_texture = NULL;
 	rsurface_glsl_uselightmap = false;
@@ -2967,10 +2967,14 @@ void RSurf_PrepareVerticesForBatch(qboolean generatenormals, qboolean generateta
 
 void RSurf_DrawBatch_Simple(int texturenumsurfaces, msurface_t **texturesurfacelist)
 {
-	int texturesurfaceindex;
+	int i, j;
 	const msurface_t *surface = texturesurfacelist[0];
-	int firstvertex = surface->num_firstvertex;
-	int endvertex = surface->num_firstvertex + surface->num_vertices;
+	const msurface_t *surface2;
+	int firstvertex;
+	int endvertex;
+	int numvertices;
+	int numtriangles;
+	// TODO: lock all array ranges before render, rather than on each surface
 	if (texturenumsurfaces == 1)
 	{
 		GL_LockArrays(surface->num_firstvertex, surface->num_vertices);
@@ -2981,68 +2985,141 @@ void RSurf_DrawBatch_Simple(int texturenumsurfaces, msurface_t **texturesurfacel
 		#define MAXBATCHTRIANGLES 4096
 		int batchtriangles = 0;
 		int batchelements[MAXBATCHTRIANGLES*3];
-		for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++)
+		for (i = 0;i < texturenumsurfaces;i = j)
 		{
-			surface = texturesurfacelist[texturesurfaceindex];
-			if (surface->num_triangles >= 256 || (batchtriangles == 0 && texturesurfaceindex + 1 >= texturenumsurfaces))
+			surface = texturesurfacelist[i];
+			j = i + 1;
+			if (surface->num_triangles > MAXBATCHTRIANGLES)
 			{
 				R_Mesh_Draw(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
 				continue;
 			}
-			if (batchtriangles + surface->num_triangles > MAXBATCHTRIANGLES)
+			memcpy(batchelements, rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle, surface->num_triangles * sizeof(int[3]));
+			batchtriangles = surface->num_triangles;
+			firstvertex = surface->num_firstvertex;
+			endvertex = surface->num_firstvertex + surface->num_vertices;
+			for (;j < texturenumsurfaces;j++)
 			{
-				R_Mesh_Draw(firstvertex, endvertex - firstvertex, batchtriangles, batchelements);
-				batchtriangles = 0;
-				firstvertex = surface->num_firstvertex;
-				endvertex = surface->num_firstvertex + surface->num_vertices;
-			}
-			else
-			{
-				firstvertex = min(firstvertex, surface->num_firstvertex);
-				endvertex = max(endvertex, surface->num_firstvertex + surface->num_vertices);
+				surface2 = texturesurfacelist[j];
+				if (batchtriangles + surface2->num_triangles > MAXBATCHTRIANGLES)
+					break;
+				memcpy(batchelements + batchtriangles * 3, rsurface_model->surfmesh.data_element3i + 3 * surface2->num_firsttriangle, surface2->num_triangles * sizeof(int[3]));
+				batchtriangles += surface2->num_triangles;
+				firstvertex = min(firstvertex, surface2->num_firstvertex);
+				endvertex = max(endvertex, surface2->num_firstvertex + surface2->num_vertices);
 			}
-			memcpy(batchelements + batchtriangles * 3, rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle, surface->num_triangles * sizeof(int[3]));
-			batchtriangles += surface->num_triangles;
+			surface2 = texturesurfacelist[j-1];
+			numvertices = endvertex - firstvertex;
+			R_Mesh_Draw(firstvertex, numvertices, batchtriangles, batchelements);
 		}
-		if (batchtriangles)
-			R_Mesh_Draw(firstvertex, endvertex - firstvertex, batchtriangles, batchelements);
 	}
 	else if (r_batchmode.integer == 1)
 	{
-		int firsttriangle = 0;
-		int endtriangle = -1;
-		for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++)
+		for (i = 0;i < texturenumsurfaces;i = j)
 		{
-			surface = texturesurfacelist[texturesurfaceindex];
-			if (surface->num_firsttriangle != endtriangle)
+			surface = texturesurfacelist[i];
+			for (j = i + 1, surface2 = surface + 1;j < texturenumsurfaces;j++, surface2++)
+				if (texturesurfacelist[j] != surface2)
+					break;
+			surface2 = texturesurfacelist[j-1];
+			numvertices = surface2->num_firstvertex + surface2->num_vertices - surface->num_firstvertex;
+			numtriangles = surface2->num_firsttriangle + surface2->num_triangles - surface->num_firsttriangle;
+			GL_LockArrays(surface->num_firstvertex, numvertices);
+			R_Mesh_Draw(surface->num_firstvertex, numvertices, numtriangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+		}
+	}
+	else
+	{
+		for (i = 0;i < texturenumsurfaces;i++)
+		{
+			surface = texturesurfacelist[i];
+			GL_LockArrays(surface->num_firstvertex, surface->num_vertices);
+			R_Mesh_Draw(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+		}
+	}
+}
+
+static void RSurf_DrawBatch_WithLightmapSwitching(int texturenumsurfaces, msurface_t **texturesurfacelist, int lightmaptexunit, int deluxemaptexunit)
+{
+	int i;
+	int j;
+	const msurface_t *surface = texturesurfacelist[0];
+	const msurface_t *surface2;
+	int firstvertex;
+	int endvertex;
+	int numvertices;
+	int numtriangles;
+	// TODO: lock all array ranges before render, rather than on each surface
+	if (texturenumsurfaces == 1)
+	{
+		R_Mesh_TexBind(lightmaptexunit, R_GetTexture(surface->lightmaptexture));
+		if (deluxemaptexunit >= 0)
+			R_Mesh_TexBind(deluxemaptexunit, R_GetTexture(surface->deluxemaptexture));
+		GL_LockArrays(surface->num_firstvertex, surface->num_vertices);
+		R_Mesh_Draw(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+	}
+	else if (r_batchmode.integer == 2)
+	{
+		#define MAXBATCHTRIANGLES 4096
+		int batchtriangles = 0;
+		int batchelements[MAXBATCHTRIANGLES*3];
+		for (i = 0;i < texturenumsurfaces;i = j)
+		{
+			surface = texturesurfacelist[i];
+			R_Mesh_TexBind(lightmaptexunit, R_GetTexture(surface->lightmaptexture));
+			if (deluxemaptexunit >= 0)
+				R_Mesh_TexBind(deluxemaptexunit, R_GetTexture(surface->deluxemaptexture));
+			j = i + 1;
+			if (surface->num_triangles > MAXBATCHTRIANGLES)
 			{
-				if (endtriangle > firsttriangle)
-				{
-					GL_LockArrays(firstvertex, endvertex - firstvertex);
-					R_Mesh_Draw(firstvertex, endvertex - firstvertex, endtriangle - firsttriangle, (rsurface_model->surfmesh.data_element3i + 3 * firsttriangle));
-				}
-				firstvertex = surface->num_firstvertex;
-				endvertex = surface->num_firstvertex + surface->num_vertices;
-				firsttriangle = surface->num_firsttriangle;
+				R_Mesh_Draw(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+				continue;
 			}
-			else
+			memcpy(batchelements, rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle, surface->num_triangles * sizeof(int[3]));
+			batchtriangles = surface->num_triangles;
+			firstvertex = surface->num_firstvertex;
+			endvertex = surface->num_firstvertex + surface->num_vertices;
+			for (;j < texturenumsurfaces;j++)
 			{
-				firstvertex = min(firstvertex, surface->num_firstvertex);
-				endvertex = max(endvertex, surface->num_firstvertex + surface->num_vertices);
+				surface2 = texturesurfacelist[j];
+				if (surface2->lightmaptexture != surface->lightmaptexture || batchtriangles + surface2->num_triangles > MAXBATCHTRIANGLES)
+					break;
+				memcpy(batchelements + batchtriangles * 3, rsurface_model->surfmesh.data_element3i + 3 * surface2->num_firsttriangle, surface2->num_triangles * sizeof(int[3]));
+				batchtriangles += surface2->num_triangles;
+				firstvertex = min(firstvertex, surface2->num_firstvertex);
+				endvertex = max(endvertex, surface2->num_firstvertex + surface2->num_vertices);
 			}
-			endtriangle = surface->num_firsttriangle + surface->num_triangles;
+			surface2 = texturesurfacelist[j-1];
+			numvertices = endvertex - firstvertex;
+			R_Mesh_Draw(firstvertex, numvertices, batchtriangles, batchelements);
 		}
-		if (endtriangle > firsttriangle)
+	}
+	else if (r_batchmode.integer == 1)
+	{
+		for (i = 0;i < texturenumsurfaces;i = j)
 		{
-			GL_LockArrays(firstvertex, endvertex - firstvertex);
-			R_Mesh_Draw(firstvertex, endvertex - firstvertex, endtriangle - firsttriangle, (rsurface_model->surfmesh.data_element3i + 3 * firsttriangle));
+			surface = texturesurfacelist[i];
+			R_Mesh_TexBind(lightmaptexunit, R_GetTexture(surface->lightmaptexture));
+			if (deluxemaptexunit >= 0)
+				R_Mesh_TexBind(deluxemaptexunit, R_GetTexture(surface->deluxemaptexture));
+			for (j = i + 1, surface2 = surface + 1;j < texturenumsurfaces;j++, surface2++)
+				if (texturesurfacelist[j] != surface2 || texturesurfacelist[j]->lightmaptexture != surface->lightmaptexture)
+					break;
+			surface2 = texturesurfacelist[j-1];
+			numvertices = surface2->num_firstvertex + surface2->num_vertices - surface->num_firstvertex;
+			numtriangles = surface2->num_firsttriangle + surface2->num_triangles - surface->num_firsttriangle;
+			GL_LockArrays(surface->num_firstvertex, numvertices);
+			R_Mesh_Draw(surface->num_firstvertex, numvertices, numtriangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
 		}
 	}
 	else
 	{
-		for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++)
+		for (i = 0;i < texturenumsurfaces;i++)
 		{
-			surface = texturesurfacelist[texturesurfaceindex];
+			surface = texturesurfacelist[i];
+			R_Mesh_TexBind(lightmaptexunit, R_GetTexture(surface->lightmaptexture));
+			if (deluxemaptexunit >= 0)
+				R_Mesh_TexBind(deluxemaptexunit, R_GetTexture(surface->deluxemaptexture));
 			GL_LockArrays(surface->num_firstvertex, surface->num_vertices);
 			R_Mesh_Draw(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, (rsurface_model->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
 		}
@@ -3134,7 +3211,7 @@ static void RSurf_DrawBatch_Lightmap(int texturenumsurfaces, msurface_t **textur
 			rsurface_lightmapcolor4f = NULL;
 		}
 	}
-	else if (lightmode >= 1 || !rsurface_lightmaptexture)
+	else if (lightmode >= 1 || !rsurface_uselightmaptexture)
 	{
 		if (texturesurfacelist[0]->lightmapinfo && texturesurfacelist[0]->lightmapinfo->stainsamples)
 		{
@@ -3236,7 +3313,10 @@ static void RSurf_DrawBatch_Lightmap(int texturenumsurfaces, msurface_t **textur
 	}
 	R_Mesh_ColorPointer(rsurface_lightmapcolor4f);
 	GL_Color(r, g, b, a);
-	RSurf_DrawBatch_Simple(texturenumsurfaces, texturesurfacelist);
+	if (rsurface_uselightmaptexture && !(rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT))
+		RSurf_DrawBatch_WithLightmapSwitching(texturenumsurfaces, texturesurfacelist, 0, -1);
+	else
+		RSurf_DrawBatch_Simple(texturenumsurfaces, texturesurfacelist);
 }
 
 static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, msurface_t **texturesurfacelist)
@@ -3318,18 +3398,18 @@ static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, msurface_t **t
 		rsurface_glsl_uselightmap = false;
 		R_Mesh_ResetTextureState();
 	}
-	if (rsurface_glsl_texture != rsurface_texture || rsurface_glsl_uselightmap != (rsurface_lightmaptexture != NULL))
+	if (rsurface_glsl_texture != rsurface_texture || rsurface_glsl_uselightmap != rsurface_uselightmaptexture)
 	{
 		int lightmode;
 		rsurface_glsl_texture = rsurface_texture;
-		rsurface_glsl_uselightmap = rsurface_lightmaptexture != NULL && !(rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT);
+		rsurface_glsl_uselightmap = rsurface_uselightmaptexture;
 		GL_DepthTest(!(rsurface_texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST));
 		GL_CullFace(((rsurface_texture->textureflags & Q3TEXTUREFLAG_TWOSIDED) || (rsurface_entity->flags & RENDER_NOCULLFACE)) ? GL_NONE : GL_FRONT); // quake is backwards, this culls back faces
 		GL_BlendFunc(rsurface_texture->currentlayers[0].blendfunc1, rsurface_texture->currentlayers[0].blendfunc2);
 		GL_DepthMask(!(rsurface_texture->currentmaterialflags & MATERIALFLAG_BLENDED));
 		GL_Color(rsurface_entity->colormod[0], rsurface_entity->colormod[1], rsurface_entity->colormod[2], rsurface_texture->currentalpha);
 		// FIXME: identify models using a better check than rsurface_model->brush.shadowmesh
-		lightmode = ((rsurface_entity->effects & EF_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
+		lightmode = ((rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
 		R_SetupSurfaceShader(vec3_origin, lightmode == 2);
 		//permutation_deluxemapping = permutation_lightmapping = R_SetupSurfaceShader(vec3_origin, lightmode == 2, false);
 		//if (r_glsl_deluxemapping.integer)
@@ -3348,9 +3428,9 @@ static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, msurface_t **t
 				R_Mesh_TexBind(8, R_GetTexture(r_texture_blanknormalmap));
 			R_Mesh_ColorPointer(NULL);
 		}
-		else if (rsurface_lightmaptexture)
+		else if (rsurface_uselightmaptexture)
 		{
-			R_Mesh_TexBind(7, R_GetTexture(rsurface_lightmaptexture));
+			R_Mesh_TexBind(7, R_GetTexture(texturesurfacelist[0]->lightmaptexture));
 			if (r_glsl_permutation->loc_Texture_Deluxemap >= 0)
 				R_Mesh_TexBind(8, R_GetTexture(texturesurfacelist[0]->deluxemaptexture));
 			R_Mesh_ColorPointer(NULL);
@@ -3367,13 +3447,10 @@ static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, msurface_t **t
 		RSurf_PrepareVerticesForBatch(true, true, texturenumsurfaces, texturesurfacelist);
 	if (!r_glsl_permutation)
 		return;
-	if (rsurface_glsl_uselightmap)
-	{
-		R_Mesh_TexBind(7, R_GetTexture(rsurface_lightmaptexture));
-		if (r_glsl_permutation->loc_Texture_Deluxemap >= 0)
-			R_Mesh_TexBind(8, R_GetTexture(texturesurfacelist[0]->deluxemaptexture));
-	}
-	RSurf_DrawBatch_Simple(texturenumsurfaces, texturesurfacelist);
+	if (rsurface_uselightmaptexture && !(rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT))
+		RSurf_DrawBatch_WithLightmapSwitching(texturenumsurfaces, texturesurfacelist, 7, r_glsl_permutation->loc_Texture_Deluxemap >= 0 ? 8 : -1);
+	else
+		RSurf_DrawBatch_Simple(texturenumsurfaces, texturesurfacelist);
 	if (rsurface_texture->backgroundnumskinframes && !(rsurface_texture->currentmaterialflags & MATERIALFLAG_TRANSPARENT))
 	{
 	}
@@ -3393,7 +3470,7 @@ static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, msurface_t **t
 	GL_DepthTest(!(rsurface_texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST));
 	GL_CullFace(((rsurface_texture->textureflags & Q3TEXTUREFLAG_TWOSIDED) || (rsurface_entity->flags & RENDER_NOCULLFACE)) ? GL_NONE : GL_FRONT); // quake is backwards, this culls back faces
 	// FIXME: identify models using a better check than rsurface_model->brush.shadowmesh
-	lightmode = ((rsurface_entity->effects & EF_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
+	lightmode = ((rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
 	if (rsurface_mode != RSURFMODE_MULTIPASS)
 		rsurface_mode = RSURFMODE_MULTIPASS;
 	RSurf_PrepareVerticesForBatch(true, false, texturenumsurfaces, texturesurfacelist);
@@ -3436,10 +3513,7 @@ static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, msurface_t **t
 		{
 		case TEXTURELAYERTYPE_LITTEXTURE:
 			memset(&m, 0, sizeof(m));
-			if (lightmode >= 1 || !rsurface_lightmaptexture)
-				m.tex[0] = R_GetTexture(r_texture_white);
-			else
-				m.tex[0] = R_GetTexture(rsurface_lightmaptexture);
+			m.tex[0] = R_GetTexture(r_texture_white);
 			m.pointer_texcoord[0] = rsurface_model->surfmesh.data_texcoordlightmap2f;
 			m.tex[1] = R_GetTexture(layer->texture);
 			m.texmatrix[1] = layer->texmatrix;
@@ -3511,7 +3585,7 @@ static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, msurface_t **t
 	GL_DepthTest(!(rsurface_texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST));
 	GL_CullFace(((rsurface_texture->textureflags & Q3TEXTUREFLAG_TWOSIDED) || (rsurface_entity->flags & RENDER_NOCULLFACE)) ? GL_NONE : GL_FRONT); // quake is backwards, this culls back faces
 	// FIXME: identify models using a better check than rsurface_model->brush.shadowmesh
-	lightmode = ((rsurface_entity->effects & EF_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
+	lightmode = ((rsurface_texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) || rsurface_model->brush.shadowmesh) ? 0 : 2;
 	if (rsurface_mode != RSURFMODE_MULTIPASS)
 		rsurface_mode = RSURFMODE_MULTIPASS;
 	RSurf_PrepareVerticesForBatch(true, false, texturenumsurfaces, texturesurfacelist);
@@ -3539,10 +3613,7 @@ static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, msurface_t **t
 				// two-pass lit texture with 2x rgbscale
 				// first the lightmap pass
 				memset(&m, 0, sizeof(m));
-				if (lightmode >= 1 || !rsurface_lightmaptexture)
-					m.tex[0] = R_GetTexture(r_texture_white);
-				else
-					m.tex[0] = R_GetTexture(rsurface_lightmaptexture);
+				m.tex[0] = R_GetTexture(r_texture_white);
 				m.pointer_texcoord[0] = rsurface_model->surfmesh.data_texcoordlightmap2f;
 				R_Mesh_TextureState(&m);
 				RSurf_DrawBatch_Lightmap(texturenumsurfaces, texturesurfacelist, 1, 1, 1, 1, lightmode, false, false);
@@ -3659,18 +3730,19 @@ static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const
 		RSurf_ActiveEntity(ent, true, r_glsl.integer && gl_support_fragment_shader);
 	batchcount = 0;
 	t = NULL;
+	rsurface_uselightmaptexture = false;
 	for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
 	{
 		msurface_t *surface = ent->model->data_surfaces + surfacelist[surfacelistindex];
 
-		if (t != surface->texture || rsurface_lightmaptexture != surface->lightmaptexture)
+		if (t != surface->texture || rsurface_uselightmaptexture != (surface->lightmaptexture != NULL))
 		{
 			if (batchcount > 0)
 				if (!(rsurface_texture->currentmaterialflags & MATERIALFLAG_SKY)) // transparent sky is too difficult
 					R_DrawTextureSurfaceList(batchcount, texturesurfacelist);
 			batchcount = 0;
 			t = surface->texture;
-			rsurface_lightmaptexture = surface->lightmaptexture;
+			rsurface_uselightmaptexture = (surface->lightmaptexture != NULL);
 			R_UpdateTextureInfo(ent, t);
 			rsurface_texture = t->currentframe;
 		}
@@ -3751,7 +3823,7 @@ void R_DrawSurfaces(entity_render_t *ent, qboolean skysurfaces)
 	flagsmask = skysurfaces ? MATERIALFLAG_SKY : (MATERIALFLAG_WATER | MATERIALFLAG_WALL);
 	f = 0;
 	t = NULL;
-	rsurface_lightmaptexture = NULL;
+	rsurface_uselightmaptexture = false;
 	rsurface_texture = NULL;
 	numsurfacelist = 0;
 	if (ent == r_refdef.worldentity)
@@ -3769,7 +3841,7 @@ void R_DrawSurfaces(entity_render_t *ent, qboolean skysurfaces)
 				// process this surface
 				surface = model->data_surfaces + j;
 				// if texture or lightmap has changed, start a new batch
-				if (t != surface->texture || rsurface_lightmaptexture != surface->lightmaptexture)
+				if (t != surface->texture || rsurface_uselightmaptexture != (surface->lightmaptexture != NULL))
 				{
 					if (numsurfacelist)
 					{
@@ -3777,7 +3849,7 @@ void R_DrawSurfaces(entity_render_t *ent, qboolean skysurfaces)
 						numsurfacelist = 0;
 					}
 					t = surface->texture;
-					rsurface_lightmaptexture = surface->lightmaptexture;
+					rsurface_uselightmaptexture = surface->lightmaptexture != NULL;
 					rsurface_texture = t->currentframe;
 					f = rsurface_texture->currentmaterialflags & flagsmask;
 				}
@@ -3806,7 +3878,7 @@ void R_DrawSurfaces(entity_render_t *ent, qboolean skysurfaces)
 		for (;surface < endsurface;surface++)
 		{
 			// if texture or lightmap has changed, start a new batch
-			if (t != surface->texture || rsurface_lightmaptexture != surface->lightmaptexture)
+			if (t != surface->texture || rsurface_uselightmaptexture != (surface->lightmaptexture != NULL))
 			{
 				if (numsurfacelist)
 				{
@@ -3814,7 +3886,7 @@ void R_DrawSurfaces(entity_render_t *ent, qboolean skysurfaces)
 					numsurfacelist = 0;
 				}
 				t = surface->texture;
-				rsurface_lightmaptexture = surface->lightmaptexture;
+				rsurface_uselightmaptexture = (surface->lightmaptexture != NULL);
 				rsurface_texture = t->currentframe;
 				f = rsurface_texture->currentmaterialflags & flagsmask;
 			}
diff --git a/render.h b/render.h
index 8406814a..d28c8118 100644
--- a/render.h
+++ b/render.h
@@ -215,7 +215,7 @@ extern qboolean rsurface_generatedvertex;
 extern const entity_render_t *rsurface_entity;
 extern const model_t *rsurface_model;
 extern texture_t *rsurface_texture;
-extern rtexture_t *rsurface_lightmaptexture;
+extern qboolean rsurface_uselightmaptexture;
 extern rsurfmode_t rsurface_mode;
 
 void RSurf_ActiveEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents);
diff --git a/todo b/todo
index cde5ae76..b4f97559 100644
--- a/todo
+++ b/todo
@@ -63,6 +63,7 @@
 0 bug darkplaces client: can't move mouse around in nexuiz menu if vid_mouse is 0
 0 bug darkplaces client: it has been reported that sometimes level changes on quakeworld servers don't load a map, this may be related to downloading? (Baker)
 0 bug darkplaces client: name (and probably other userinfo properties) are not being set when entering a qw server?
+0 bug darkplaces client: when going through a teleporter the cl_movement prediction still interpolates the move (div0)
 0 bug darkplaces loader: make rtlight entity loader support q3map/q3map2 lights properly, they use a spawnflag for LINEAR mode, by default they use 1/(x*x) falloff (Carni, motorsep)
 0 bug darkplaces physics: when riding a lift down (such as near the start of e1m1), the player is not being pulled down, unlike in quake, this can cause repeated fall damage on very fast lifts (scar3crow)
 0 bug darkplaces readme: commandline options are slightly out of date, update them (Baker)
-- 
2.39.5