diff --git a/src/m_menu.c b/src/m_menu.c
index 1f7cbdcf401ad9f0662d5a1bd713e221f455bb1b..87b9c5c435c053cbbdc18747f510f35f5bb9d17b 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1345,9 +1345,10 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "NiGHTS Hoop Draw Dist.", &cv_drawdist_nights, 201},
 
 	{IT_HEADER, NULL, "Diagnostic", NULL, 210},
-	{IT_STRING | IT_CVAR, NULL, "Show FPS",            &cv_ticrate,         216},
-	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw", &cv_homremoval,      221},
-	{IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost,   226},
+	{IT_STRING | IT_CVAR, NULL, "Show FPS",            &cv_fpscounter,      216},
+	{IT_STRING | IT_CVAR, NULL, "Show TPS",            &cv_tpscounter,      221},
+	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw", &cv_homremoval,      226},
+	{IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost,   231},
 };
 
 static menuitem_t OP_VideoModeMenu[] =
diff --git a/src/screen.c b/src/screen.c
index 5d16b2bc0a565a347b6a7d2f289059c32d88c318..acb0c55b6337405b48f91049a985655997495423 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -221,7 +221,8 @@ void SCR_Startup(void)
 	V_Init();
 	V_Recalc();
 
-	CV_RegisterVar(&cv_ticrate);
+	CV_RegisterVar(&cv_fpscounter);
+	CV_RegisterVar(&cv_tpscounter);
 	CV_RegisterVar(&cv_constextsize);
 
 	V_SetPalette(0);
@@ -488,6 +489,11 @@ INT32 SCR_GetModeForSize(INT32 w, INT32 h)
 	return -1;
 }
 
+// XMOD FPS display
+// moved out of os-specific code for consistency
+static boolean ticsgraph[TICRATE];
+static tic_t lasttic;
+
 double averageFPS = 0.0f;
 
 #define USE_FPS_SAMPLES
@@ -535,31 +541,48 @@ void SCR_CalculateFPS(void)
 
 void SCR_DisplayTicRate(void)
 {
-	INT32 ticcntcolor = 0;
+	INT32 fpscntcolor = 0, ticcntcolor = 0;
 	const INT32 h = vid.height-(8*vid.dup);
 	UINT32 cap = R_GetFramerateCap();
 	double fps = round(averageFPS);
+	INT32 hstep = 0;
+	tic_t i;
+	tic_t ontic = I_GetTime();
+	tic_t totaltics = 0;
 
 	if (gamestate == GS_NULL)
 		return;
 
 	if (cap > 0)
 	{
-		if (fps <= cap / 2.0) ticcntcolor = V_REDMAP;
-		else if (fps <= cap * 0.90) ticcntcolor = V_YELLOWMAP;
-		else ticcntcolor = V_GREENMAP;
+		if (fps <= cap / 2.0) fpscntcolor = V_REDMAP;
+		else if (fps <= cap * 0.90) fpscntcolor = V_YELLOWMAP;
+		else fpscntcolor = V_GREENMAP;
 	}
 	else
 	{
-		ticcntcolor = V_GREENMAP;
+		fpscntcolor = V_GREENMAP;
 	}
 
-	if (cv_ticrate.value == 2) // compact counter
+	for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i)
+	ticsgraph[i % TICRATE] = false;
+
+	ticsgraph[ontic % TICRATE] = true;
+
+	for (i = 0;i < TICRATE;++i)
+		if (ticsgraph[i])
+			++totaltics;
+
+	if (totaltics <= TICRATE/2) ticcntcolor = V_REDMAP;
+	else if (totaltics >= TICRATE) ticcntcolor = V_BLUEMAP;
+
+	if (cv_fpscounter.value == 2) // compact counter
 	{
 		V_DrawRightAlignedString(vid.width, h,
-			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%04.2f", averageFPS)); // use averageFPS directly
+			fpscntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%04.2f", averageFPS)); // use averageFPS directly
+		hstep = 8*vid.dup;
 	}
-	else if (cv_ticrate.value == 1) // full counter
+	else if (cv_fpscounter.value == 1) // full counter
 	{
 		const char *drawnstr;
 		INT32 width;
@@ -573,10 +596,25 @@ void SCR_DisplayTicRate(void)
 		width = V_StringWidth(drawnstr, V_NOSCALESTART);
 
 		V_DrawString(vid.width - ((7 * 8 * vid.dup) + V_StringWidth("FPS: ", V_NOSCALESTART)), h,
-			V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS:");
+			V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS: ");
 		V_DrawString(vid.width - width, h,
-			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, drawnstr);
+			fpscntcolor|V_NOSCALESTART|V_USERHUDTRANS, drawnstr);
+		hstep = 8*vid.dup;
+	}
+
+	if (cv_tpscounter.value == 2) // compact counter
+	{
+		V_DrawRightAlignedString(vid.width, h-hstep,
+			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d", totaltics));
+	}
+	else if (cv_tpscounter.value == 1) // full counter
+	{
+		V_DrawString(vid.width - ((7 * 8 * vid.dup) + V_StringWidth("TPS: ", V_NOSCALESTART)), h-hstep,
+			V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "TPS: ");
+		V_DrawRightAlignedString(vid.width, h-hstep,
+			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d/%02u", totaltics, TICRATE));
 	}
+	lasttic = ontic;
 }
 
 void SCR_DisplayLocalPing(void)
@@ -584,7 +622,11 @@ void SCR_DisplayLocalPing(void)
 	UINT32 ping = playerpingtable[consoleplayer];	// consoleplayer's ping is everyone's ping in a splitnetgame :P
 	if (cv_showping.value == 1 || (cv_showping.value == 2 && servermaxping && ping > servermaxping))	// only show 2 (warning) if our ping is at a bad level
 	{
-		INT32 dispy = cv_ticrate.value ? 180 : 189;
+		INT32 dispy = 189;
+		if(cv_tpscounter.value && cv_fpscounter.value)
+			dispy = 172;
+		else if (cv_tpscounter.value || cv_fpscounter.value)
+			dispy = 180;
 		HU_drawPing(307, dispy, ping, true, V_SNAPTORIGHT | V_SNAPTOBOTTOM);
 	}
 }
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 2a6e4bade2a9401575b100fcf634c474d772ad56..a2001d1c15cb40bd5754afcdbb0e2ca0538d4fcc 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1352,7 +1352,7 @@ void I_FinishUpdate(void)
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
 
-	if (cv_ticrate.value)
+	if (cv_fpscounter.value || cv_tpscounter.value)
 		SCR_DisplayTicRate();
 
 	if (cv_showping.value && netgame && consoleplayer != serverplayer)
diff --git a/src/v_video.c b/src/v_video.c
index 39154e3ed5c6e3620c8f732cabd9b601811cd7b0..93a54168990534ed953bc8c8c450a6904c61bd0a 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -42,8 +42,9 @@ UINT8 *screens[5];
 // screens[3] = fade screen start
 // screens[4] = fade screen end, postimage tempoarary buffer
 
-static CV_PossibleValue_t ticrate_cons_t[] = {{0, "No"}, {1, "Full"}, {2, "Compact"}, {0, NULL}};
-consvar_t cv_ticrate = CVAR_INIT ("showfps", "No", CV_SAVE, ticrate_cons_t, NULL);
+static CV_PossibleValue_t fpscounter_cons_t[] = {{0, "No"}, {1, "Full"}, {2, "Compact"}, {0, NULL}};
+consvar_t cv_fpscounter = CVAR_INIT ("showfps", "No", CV_SAVE, fpscounter_cons_t, NULL);
+consvar_t cv_tpscounter = CVAR_INIT ("showtps", "No", CV_SAVE, fpscounter_cons_t, NULL);
 
 static void CV_palette_OnChange(void);
 
diff --git a/src/v_video.h b/src/v_video.h
index bdab23e8ad6cbd0c5868525da6f91500742e7e0f..a8904beac989b67250ce751832fc150f08606936 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -28,7 +28,7 @@
 
 extern UINT8 *screens[5];
 
-extern consvar_t cv_ticrate, cv_constextsize,
+extern consvar_t cv_fpscounter, cv_tpscounter, cv_constextsize,
 cv_globalgamma, cv_globalsaturation,
 cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue,
 cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma,