diff --git a/engine/client/menu.c b/engine/client/menu.c
index e254b1d14..1118afeff 100644
--- a/engine/client/menu.c
+++ b/engine/client/menu.c
@@ -179,7 +179,7 @@ void M_DrawTextBox (int x, int y, int width, int lines)
 {
 	mpic_t	*p;
 	int		cx, cy;
-	int		n;
+	int		n, w;
 
 	// draw left side
 	cx = x;
@@ -208,33 +208,39 @@ void M_DrawTextBox (int x, int y, int width, int lines)
 		M_DrawScalePic (cx, cy+8, 8, 8, p);
 
 	// draw middle
+	cy = y;
 	cx += 8;
-	while (width > 0)
+	//top-strip
+	p = R2D_SafeCachePic ("gfx/box_tm.lmp");
+	if (p) for (w = 0; w < width; w+=2)
+		M_DrawScalePic (cx+w*8, cy, 16, 8, p);
+
+	//just-under-top (shadowed region)
+	if (lines)
 	{
-		cy = y;
-		p = R2D_SafeCachePic ("gfx/box_tm.lmp");
-		if (p)
-			M_DrawScalePic (cx, cy, 16, 8, p);
+		cy+=8;
 		p = R2D_SafeCachePic ("gfx/box_mm.lmp");
-		if (p)
-			for (n = 0; n < lines; n++)
-			{
-				cy += 8;
-				if (n == 1)
-				{
-					p = R2D_SafeCachePic ("gfx/box_mm2.lmp");
-					if (!p)
-						break;
-				}
-				M_DrawScalePic (cx, cy, 16, 8, p);
-			}
-		p = R2D_SafeCachePic ("gfx/box_bm.lmp");
-		if (p)
-			M_DrawScalePic (cx, cy+8, 16, 8, p);
-		width -= 2;
-		cx += 16;
+		if (p) for (w = 0; w < width; w+=2)
+			M_DrawScalePic (cx+w*8, cy, 16, 8, p);
 	}
 
+	//2d body
+	p = R2D_SafeCachePic ("gfx/box_mm2.lmp");
+	for (n = 1; n < lines; n++)
+	{
+		cy+=8;
+		if (p) for (w = 0; w < width; w+=2)
+			M_DrawScalePic (cx+w*8, cy, 16, 8, p);
+	}
+
+	//bottom strip
+	cy+=8;
+	p = R2D_SafeCachePic ("gfx/box_bm.lmp");
+	if (p) for (w = 0; w < width; w+=2)
+		M_DrawScalePic (cx+w*8, cy, 16, 8, p);
+
+	cx += 8*width;
+
 	// draw right side
 	cy = y;
 	p = R2D_SafeCachePic ("gfx/box_tr.lmp");
diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c
index cd4a38322..56aed62b3 100644
--- a/engine/client/r_surf.c
+++ b/engine/client/r_surf.c
@@ -56,6 +56,8 @@ extern cvar_t r_stainfadeammount;
 extern cvar_t r_lightmap_nearest;
 extern cvar_t r_lightmap_format;
 
+double r_loaderstalltime;
+
 extern int r_dlightframecount;
 
 static void Surf_FreeLightmap(lightmapinfo_t *lm);
@@ -2484,6 +2486,7 @@ static void R_DestroyWorldEBO(struct webostate_s *es)
 }
 void R_GeneratedWorldEBO(void *ctx, void *data, size_t a_, size_t b_)
 {
+	double starttime = Sys_DoubleTime();
 	size_t idxcount, vertcount;
 	unsigned int i;
 	model_t *mod;
@@ -2648,6 +2651,8 @@ void R_GeneratedWorldEBO(void *ctx, void *data, size_t a_, size_t b_)
 			webostate->rbatches[sortid] = b;
 		}
 	}
+
+	r_loaderstalltime += Sys_DoubleTime() - starttime;
 }
 #ifdef Q1BSPS
 static void Surf_SimpleWorld_Q1BSP(struct webostate_s *es, qbyte *pvs)
diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c
index 3e0d35b3e..ffc392035 100644
--- a/engine/gl/gl_backend.c
+++ b/engine/gl/gl_backend.c
@@ -1000,7 +1000,7 @@ void GL_CullFace(unsigned int sflags)
 	}
 }
 
-void R_FetchPlayerColour(unsigned int cv, vec3_t rgb)
+static void R_FetchPlayerColour(unsigned int cv, vec3_t rgb)
 {
 	int i;
 
@@ -5530,8 +5530,10 @@ void GLBE_SubmitMeshes (batch_t **worldbatches, int start, int stop)
 #define THREADEDWORLD
 #endif
 
+extern double r_loaderstalltime;
 void GLBE_UpdateLightmaps(void)
 {
+	double starttime;
 	lightmapinfo_t *lm;
 	int lmidx;
 
@@ -5542,6 +5544,7 @@ void GLBE_UpdateLightmaps(void)
 	webo_blocklightmapupdates |= 2;	//FIXME: round-robin it? one lightmap per frame?
 #endif
 
+	starttime = Sys_DoubleTime();
 	for (lmidx = 0; lmidx < numlightmaps; lmidx++)
 	{
 		lm = lightmap[lmidx];
@@ -5615,6 +5618,7 @@ void GLBE_UpdateLightmaps(void)
 			lm->rectchange.b = 0;
 		}
 	}
+	r_loaderstalltime += Sys_DoubleTime()-starttime;
 }
 
 batch_t *GLBE_GetTempBatch(void)
diff --git a/engine/gl/gl_ngraph.c b/engine/gl/gl_ngraph.c
index bb0aad04f..622bc3200 100644
--- a/engine/gl/gl_ngraph.c
+++ b/engine/gl/gl_ngraph.c
@@ -213,19 +213,22 @@ void R_NetGraph (void)
 			Vector4Copy(rgba[2], rgba[1]);
 		}
 
-		R2D_Image2dQuad((const vec2_t*)p, (const vec2_t*)tc, (const vec4_t*)rgba, shader_draw_fill);
+		if (a)
+			R2D_Image2dQuad((const vec2_t*)p, (const vec2_t*)tc, (const vec4_t*)rgba, shader_draw_fill);
 	}
 #endif
 }
 
 void R_FrameTimeGraph (float frametime, float scale)
 {
-	float bias = 0;
-	int		a, x, i, y, h, col;
+	float bias = 0, h, lh;
+	int		a, b, x, i, y;
 
-	vec2_t p[4];
-	vec2_t tc[4];
-	vec4_t rgba[4];
+	struct{
+		vec2_t xy[4];
+		vec2_t tc[4];
+		vec4_t rgba[4];
+	} g[3];
 	extern shader_t *shader_draw_fill;
 
 	conchar_t line[128];
@@ -234,17 +237,25 @@ void R_FrameTimeGraph (float frametime, float scale)
 
 	static struct
 	{
-		float time;
-		int col;
+		float time[countof(g)];
 	} history[NET_TIMINGS];
 	static unsigned int findex;
 
 #ifdef LOADERTHREAD
 	extern int com_hadwork[WG_COUNT];
 #endif
+	extern double server_frametime, r_loaderstalltime;
+
+	history[findex&NET_TIMINGSMASK].time[0] = max(0,frametime);	//server band
+#ifdef HAVE_SERVER
+	frametime -= server_frametime; server_frametime = 0;
+#endif
+
+	history[findex&NET_TIMINGSMASK].time[1] = max(0,frametime);	//stalls band
+	frametime -= r_loaderstalltime; r_loaderstalltime = 0;
+
+	history[findex&NET_TIMINGSMASK].time[2] = max(0,frametime);	//client band (max is needed because we might have been failing to clear the other timers)
 
-	history[findex&NET_TIMINGSMASK].time = frametime;
-	history[findex&NET_TIMINGSMASK].col = 0xffffffff;
 	findex++;
 
 #ifdef LOADERTHREAD
@@ -252,20 +263,20 @@ void R_FrameTimeGraph (float frametime, float scale)
 	{	//recolour the graph red if the main thread processed something from a worker.
 		//show three, because its not so easy to see when its whizzing past.
 		com_hadwork[WG_MAIN] = 0;
-		history[(findex-1)&NET_TIMINGSMASK].col = 0xff0000ff;
-		history[(findex-2)&NET_TIMINGSMASK].col = 0xff0000ff;
-		history[(findex-3)&NET_TIMINGSMASK].col = 0xff0000ff;
+//		history[(findex-1)&NET_TIMINGSMASK].col = 0xff0000ff;
+//		history[(findex-2)&NET_TIMINGSMASK].col = 0xff0000ff;
+//		history[(findex-3)&NET_TIMINGSMASK].col = 0xff0000ff;
 	}
 #endif
 
 	x = 0;
 	for (a=0 ; a<NET_TIMINGS ; a++)
 	{
-		avg     += history[a].time;
-		if (minv > history[a].time)
-			minv = history[a].time;
-		if (maxv < history[a].time)
-			maxv = history[a].time;
+		avg     += history[a].time[0];
+		if (minv > history[a].time[0])
+			minv = history[a].time[0];
+		if (maxv < history[a].time[0])
+			maxv = history[a].time[0];
 	}
 	if (!scale)
 	{
@@ -276,7 +287,7 @@ void R_FrameTimeGraph (float frametime, float scale)
 		scale *= 1000;
 	avg/=a;
 	for (a = 0; a < NET_TIMINGS; a++)
-		dev += 1000*1000*(history[a].time - avg)*(history[a].time - avg);
+		dev += 1000*1000*(history[a].time[0] - avg)*(history[a].time[0] - avg);
 	dev /= a;
 	dev = sqrt(dev);
 
@@ -306,31 +317,42 @@ void R_FrameTimeGraph (float frametime, float scale)
 	Draw_ExpandedString(font_console, x, y, line);
 	y += Font_CharVHeight(font_console);
 
-	Vector2Set(p[2], 0,0);
-	Vector2Set(p[3], 0,0);
-	Vector4Set(rgba[2], 0,0,0,0);
-	Vector4Set(rgba[3], 0,0,0,0);
+	for (b = 0; b < countof(g); b++)
+	{
+		Vector2Set(g[b].xy[2], 0,0);
+		Vector2Set(g[b].xy[3], 0,0);
+	}
+	for (a=0 ; a<4 ; a++)
+	{
+		Vector4Set(g[0].rgba[a], 1.0,0.1,0.1,1.0);	//server = red
+		Vector4Set(g[1].rgba[a], 0.1,1.0,0.1,1.0);	//lightmap/stalls = green
+		Vector4Set(g[2].rgba[a], 1.0,1.0,1.0,1.0);	//client/other = white.
+	}
+
 	for (a=0 ; a<NET_TIMINGS ; a++)
 	{
 		i = (findex-NET_TIMINGS+a)&(NET_TIMINGS-1);
-		h = (history[i].time - bias) * scale;
-		col = history[i].col;
+		lh = NET_GRAPHHEIGHT;
+		for (b = countof(g); b-- > 0; lh = h)
+		{
+			h = (history[i].time[b]-bias) * scale;
 
-		if (h > NET_GRAPHHEIGHT)
-			h = NET_GRAPHHEIGHT;
-		Vector2Copy(p[3], p[0]);	Vector4Copy(rgba[3], rgba[0]);
-		Vector2Copy(p[2], p[1]);	Vector4Copy(rgba[2], rgba[1]);
+			if (h > NET_GRAPHHEIGHT)
+				h = NET_GRAPHHEIGHT;
+			h = (NET_GRAPHHEIGHT-h);
 
-		Vector2Set(p[2+0], x+a,		y+(NET_GRAPHHEIGHT-h));
-		Vector2Set(p[2+1], x+a,		y+NET_GRAPHHEIGHT);
+			Vector2Copy(g[b].xy[3], g[b].xy[0]);	Vector4Copy(g[b].rgba[3], g[b].rgba[0]);
+			Vector2Copy(g[b].xy[2], g[b].xy[1]);	Vector4Copy(g[b].rgba[2], g[b].rgba[1]);
 
-		Vector2Set(tc[2+0], x/(float)NET_TIMINGS,		(NET_GRAPHHEIGHT-h)/NET_GRAPHHEIGHT);
-		Vector2Set(tc[2+1], x/(float)NET_TIMINGS,		1);
-		Vector4Set(rgba[2+0], ((col>>0)&0xff)/255.0, ((col>>8)&0xff)/255.0, ((col>>16)&0xff)/255.0, ((col>>24)&0xff)/255.0);
-		Vector4Copy(rgba[2+0], rgba[2+1]);
+			Vector2Set(g[b].xy[2+0], x+a,		y+h);
+			Vector2Set(g[b].xy[2+1], x+a,		y+lh);
 
-		if (a)
-			R2D_Image2dQuad((const vec2_t*)p, (const vec2_t*)tc, (const vec4_t*)rgba, shader_draw_fill);
+			Vector2Set(g[b].tc[2+0], x/(float)NET_TIMINGS,		(NET_GRAPHHEIGHT-h)/NET_GRAPHHEIGHT);
+			Vector2Set(g[b].tc[2+1], x/(float)NET_TIMINGS,		1);
+
+			if (a && (h!=lh || g[b].xy[0][1]!=g[b].xy[1][1]))
+				R2D_Image2dQuad((const vec2_t*)g[b].xy, (const vec2_t*)g[b].tc, (const vec4_t*)g[b].rgba, shader_draw_fill);
+		}
 	}
 }
 
diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c
index 17b42a660..c2ca594b2 100644
--- a/engine/server/sv_main.c
+++ b/engine/server/sv_main.c
@@ -5286,6 +5286,7 @@ static void SV_PauseChanged(void)
 #endif
 }
 
+double server_frametime;
 /*
 ==================
 SV_Frame
@@ -5590,6 +5591,8 @@ float SV_Frame (void)
 		svs.stats.maxpackets = 0;
 	}
 	oldpackets = svs.stats.packets;
+
+	server_frametime += end-start;
 	return delay;
 }