diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e314d419f46c3250e3436dbca2739a189115f2ad..d9a7ce62ceae935bf068794d9093a373a98f109f 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -5445,14 +5445,14 @@ void TryRunTics(tic_t realtics)
 			{
 				DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
 
-				ps_tictime = I_GetTimeMicros();
+				ps_tictime = I_GetPreciseTime();
 
 				G_Ticker((gametic % NEWTICRATERATIO) == 0);
 				ExtraDataTicker();
 				gametic++;
 				consistancy[gametic%BACKUPTICS] = Consistancy();
 
-				ps_tictime = I_GetTimeMicros() - ps_tictime;
+				ps_tictime = I_GetPreciseTime() - ps_tictime;
 
 				// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
 				if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
diff --git a/src/d_main.c b/src/d_main.c
index ce1331fe3abf12eb1f0a9dfb01d01777ecaf7959..caf6f0c22e0cf54f26c4e0024ec94983e1640dce 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -436,7 +436,7 @@ static void D_Display(void)
 
 			if (!automapactive && !dedicated && cv_renderview.value)
 			{
-				ps_rendercalltime = I_GetTimeMicros();
+				ps_rendercalltime = I_GetPreciseTime();
 				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
 				{
 					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
@@ -483,7 +483,7 @@ static void D_Display(void)
 					if (postimgtype2)
 						V_DoPostProcessor(1, postimgtype2, postimgparam2);
 				}
-				ps_rendercalltime = I_GetTimeMicros() - ps_rendercalltime;
+				ps_rendercalltime = I_GetPreciseTime() - ps_rendercalltime;
 			}
 
 			if (lastdraw)
@@ -497,7 +497,7 @@ static void D_Display(void)
 				lastdraw = false;
 			}
 
-			ps_uitime = I_GetTimeMicros();
+			ps_uitime = I_GetPreciseTime();
 
 			if (gamestate == GS_LEVEL)
 			{
@@ -510,7 +510,7 @@ static void D_Display(void)
 		}
 		else
 		{
-			ps_uitime = I_GetTimeMicros();
+			ps_uitime = I_GetPreciseTime();
 		}
 	}
 
@@ -552,7 +552,7 @@ static void D_Display(void)
 
 	CON_Drawer();
 
-	ps_uitime = I_GetTimeMicros() - ps_uitime;
+	ps_uitime = I_GetPreciseTime() - ps_uitime;
 
 	//
 	// wipe update
@@ -638,9 +638,9 @@ static void D_Display(void)
 			M_DrawPerfStats();
 		}
 
-		ps_swaptime = I_GetTimeMicros();
+		ps_swaptime = I_GetPreciseTime();
 		I_FinishUpdate(); // page flip or blit buffer
-		ps_swaptime = I_GetTimeMicros() - ps_swaptime;
+		ps_swaptime = I_GetPreciseTime() - ps_swaptime;
 	}
 
 	needpatchflush = false;
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index 5ea9f55d4c9ba88cc36ffa6ba2e44f72ce79ff60..fb3417158a76a53abd38698da07425541ffddb02 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -248,12 +248,12 @@ void HWR_RenderBatches(void)
 	}
 
 	// sort polygons
-	ps_hw_batchsorttime = I_GetTimeMicros();
+	ps_hw_batchsorttime = I_GetPreciseTime();
 	if (cv_glshaders.value && gl_shadersavailable)
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygons);
 	else
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygonsNoShaders);
-	ps_hw_batchsorttime = I_GetTimeMicros() - ps_hw_batchsorttime;
+	ps_hw_batchsorttime = I_GetPreciseTime() - ps_hw_batchsorttime;
 	// sort order
 	// 1. shader
 	// 2. texture
@@ -261,7 +261,7 @@ void HWR_RenderBatches(void)
 	// 4. colors + light level
 	// not sure about what order of the last 2 should be, or if it even matters
 
-	ps_hw_batchdrawtime = I_GetTimeMicros();
+	ps_hw_batchdrawtime = I_GetPreciseTime();
 
 	currentShader = polygonArray[polygonIndexArray[0]].shader;
 	currentTexture = polygonArray[polygonIndexArray[0]].texture;
@@ -446,7 +446,7 @@ void HWR_RenderBatches(void)
 	polygonArraySize = 0;
 	unsortedVertexArraySize = 0;
 
-	ps_hw_batchdrawtime = I_GetTimeMicros() - ps_hw_batchdrawtime;
+	ps_hw_batchdrawtime = I_GetPreciseTime() - ps_hw_batchdrawtime;
 }
 
 
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 4268556e3b02508e8ee25ce04caad0ac78824e7a..3df494272a106690e276a3f137dbde080bf937ce 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -146,11 +146,11 @@ static angle_t gl_aimingangle;
 static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
 
 // Render stats
-int ps_hw_skyboxtime = 0;
-int ps_hw_nodesorttime = 0;
-int ps_hw_nodedrawtime = 0;
-int ps_hw_spritesorttime = 0;
-int ps_hw_spritedrawtime = 0;
+precise_t ps_hw_skyboxtime = 0;
+precise_t ps_hw_nodesorttime = 0;
+precise_t ps_hw_nodedrawtime = 0;
+precise_t ps_hw_spritesorttime = 0;
+precise_t ps_hw_spritedrawtime = 0;
 
 // Render stats for batching
 int ps_hw_numpolys = 0;
@@ -160,8 +160,8 @@ int ps_hw_numshaders = 0;
 int ps_hw_numtextures = 0;
 int ps_hw_numpolyflags = 0;
 int ps_hw_numcolors = 0;
-int ps_hw_batchsorttime = 0;
-int ps_hw_batchdrawtime = 0;
+precise_t ps_hw_batchsorttime = 0;
+precise_t ps_hw_batchdrawtime = 0;
 
 boolean gl_shadersavailable = true;
 
@@ -4503,7 +4503,7 @@ static void HWR_CreateDrawNodes(void)
 	// that is already lying around. This should all be in some sort of linked list or lists.
 	sortindex = Z_Calloc(sizeof(size_t) * (numplanes + numpolyplanes + numwalls), PU_STATIC, NULL);
 
-	ps_hw_nodesorttime = I_GetTimeMicros();
+	ps_hw_nodesorttime = I_GetPreciseTime();
 
 	for (i = 0; i < numplanes; i++, p++)
 	{
@@ -4558,9 +4558,9 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	ps_hw_nodesorttime = I_GetTimeMicros() - ps_hw_nodesorttime;
+	ps_hw_nodesorttime = I_GetPreciseTime() - ps_hw_nodesorttime;
 
-	ps_hw_nodedrawtime = I_GetTimeMicros();
+	ps_hw_nodedrawtime = I_GetPreciseTime();
 
 	// Okay! Let's draw it all! Woo!
 	HWD.pfnSetTransform(&atransform);
@@ -4597,7 +4597,7 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	ps_hw_nodedrawtime = I_GetTimeMicros() - ps_hw_nodedrawtime;
+	ps_hw_nodedrawtime = I_GetPreciseTime() - ps_hw_nodedrawtime;
 
 	numwalls = 0;
 	numplanes = 0;
@@ -5778,10 +5778,10 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	if (viewnumber == 0) // Only do it if it's the first screen being rendered
 		HWD.pfnClearBuffer(true, false, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
 
-	ps_hw_skyboxtime = I_GetTimeMicros();
+	ps_hw_skyboxtime = I_GetPreciseTime();
 	if (skybox && drawsky) // If there's a skybox and we should be drawing the sky, draw the skybox
 		HWR_RenderSkyboxView(viewnumber, player); // This is drawn before everything else so it is placed behind
-	ps_hw_skyboxtime = I_GetTimeMicros() - ps_hw_skyboxtime;
+	ps_hw_skyboxtime = I_GetPreciseTime() - ps_hw_skyboxtime;
 
 	{
 		// do we really need to save player (is it not the same)?
@@ -5894,7 +5894,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	ps_numbspcalls = 0;
 	ps_numpolyobjects = 0;
-	ps_bsptime = I_GetTimeMicros();
+	ps_bsptime = I_GetPreciseTime();
 
 	validcount++;
 
@@ -5932,7 +5932,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 #endif
 
-	ps_bsptime = I_GetTimeMicros() - ps_bsptime;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
 
 	if (cv_glbatching.value)
 		HWR_RenderBatches();
@@ -5948,12 +5948,12 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	// Draw MD2 and sprites
 	ps_numsprites = gl_visspritecount;
-	ps_hw_spritesorttime = I_GetTimeMicros();
+	ps_hw_spritesorttime = I_GetPreciseTime();
 	HWR_SortVisSprites();
-	ps_hw_spritesorttime = I_GetTimeMicros() - ps_hw_spritesorttime;
-	ps_hw_spritedrawtime = I_GetTimeMicros();
+	ps_hw_spritesorttime = I_GetPreciseTime() - ps_hw_spritesorttime;
+	ps_hw_spritedrawtime = I_GetPreciseTime();
 	HWR_DrawSprites();
-	ps_hw_spritedrawtime = I_GetTimeMicros() - ps_hw_spritedrawtime;
+	ps_hw_spritedrawtime = I_GetPreciseTime() - ps_hw_spritedrawtime;
 
 #ifdef NEWCORONAS
 	//Hurdler: they must be drawn before translucent planes, what about gl fog?
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 85072dfd9c483456206d0315f248d606cd18f380..4db0a92310abe22dbb13c9acb6e2ae90b54fd89c 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -113,11 +113,11 @@ extern FTransform atransform;
 
 
 // Render stats
-extern int ps_hw_skyboxtime;
-extern int ps_hw_nodesorttime;
-extern int ps_hw_nodedrawtime;
-extern int ps_hw_spritesorttime;
-extern int ps_hw_spritedrawtime;
+extern precise_t ps_hw_skyboxtime;
+extern precise_t ps_hw_nodesorttime;
+extern precise_t ps_hw_nodedrawtime;
+extern precise_t ps_hw_spritesorttime;
+extern precise_t ps_hw_spritedrawtime;
 
 // Render stats for batching
 extern int ps_hw_numpolys;
@@ -127,8 +127,8 @@ extern int ps_hw_numshaders;
 extern int ps_hw_numtextures;
 extern int ps_hw_numpolyflags;
 extern int ps_hw_numcolors;
-extern int ps_hw_batchsorttime;
-extern int ps_hw_batchdrawtime;
+extern precise_t ps_hw_batchsorttime;
+extern precise_t ps_hw_batchdrawtime;
 
 extern boolean gl_shadersavailable;
 
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 65d483dc1a9f1518fab22a70adde2bc78aa20ad3..602dbf0ec9ab417f887baf1ea32d9ac0e7ba9311 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -25,7 +25,7 @@
 
 #include "m_perfstats.h"
 #include "d_netcmd.h" // for cv_perfstats
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 static UINT8 hooksAvailable[(hook_MAX/8)+1];
 
@@ -480,7 +480,7 @@ void LUAh_ThinkFrame(void)
 			continue;
 
 		if (cv_perfstats.value == 3)
-			time_taken = I_GetTimeMicros();
+			time_taken = I_GetPreciseTime();
 		PushHook(gL, hookp);
 		if (lua_pcall(gL, 0, 0, 1)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
@@ -491,7 +491,7 @@ void LUAh_ThinkFrame(void)
 		if (cv_perfstats.value == 3)
 		{
 			lua_Debug ar;
-			time_taken = I_GetTimeMicros() - time_taken;
+			time_taken = I_GetPreciseTime() - time_taken;
 			// we need the function, let's just retrieve it again
 			PushHook(gL, hookp);
 			lua_getinfo(gL, ">S", &ar);
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index 085adda80dac925917e2f86ec3a14ec4ec5c0590..1596a87e5a841d019513c706b13608259334d7e7 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -22,32 +22,37 @@
 #include "hardware/hw_main.h"
 #endif
 
-int ps_tictime = 0;
-
-int ps_playerthink_time = 0;
-int ps_thinkertime = 0;
-
-int ps_thlist_times[NUM_THINKERLISTS];
-static const char* thlist_names[] = {
-	"Polyobjects:     %d",
-	"Main:            %d",
-	"Mobjs:           %d",
-	"Dynamic slopes:  %d",
-	"Precipitation:   %d",
-	NULL
+struct perfstatcol;
+struct perfstatrow;
+
+typedef struct perfstatcol perfstatcol_t;
+typedef struct perfstatrow perfstatrow_t;
+
+struct perfstatcol {
+	INT32 lores_x;
+	INT32 hires_x;
+	INT32 color;
+	perfstatrow_t * rows;
 };
-static const char* thlist_shortnames[] = {
-	"plyobjs %d",
-	"main    %d",
-	"mobjs   %d",
-	"dynslop %d",
-	"precip  %d",
-	NULL
+
+struct perfstatrow {
+	const char * lores_label;
+	const char * hires_label;
+	void       * value;
 };
 
+static precise_t ps_frametime = 0;
+
+precise_t ps_tictime = 0;
+
+precise_t ps_playerthink_time = 0;
+precise_t ps_thinkertime = 0;
+
+precise_t ps_thlist_times[NUM_THINKERLISTS];
+
 int ps_checkposition_calls = 0;
 
-int ps_lua_thinkframe_time = 0;
+precise_t ps_lua_thinkframe_time = 0;
 int ps_lua_mobjhooks = 0;
 
 // dynamically allocated resizeable array for thinkframe hook stats
@@ -55,6 +60,8 @@ ps_hookinfo_t *thinkframe_hooks = NULL;
 int thinkframe_hooks_length = 0;
 int thinkframe_hooks_capacity = 16;
 
+static INT32 draw_row;
+
 void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src)
 {
 	if (!thinkframe_hooks)
@@ -76,380 +83,414 @@ void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src)
 	thinkframe_hooks_length = index + 1;
 }
 
-void M_DrawPerfStats(void)
+static void PS_SetFrameTime(void)
 {
-	char s[100];
-	int currenttime = I_GetTimeMicros();
-	int frametime = currenttime - ps_prevframetime;
+	precise_t currenttime = I_GetPreciseTime();
+	ps_frametime = currenttime - ps_prevframetime;
 	ps_prevframetime = currenttime;
+}
 
-	if (cv_perfstats.value == 1) // rendering
+static boolean M_HighResolution(void)
+{
+	return (vid.width >= 640 && vid.height >= 400);
+}
+
+enum {
+	PERF_TIME,
+	PERF_COUNT,
+};
+
+static void M_DrawPerfString(perfstatcol_t *col, int type)
+{
+	const boolean hires = M_HighResolution();
+
+	INT32 draw_flags = V_MONOSPACE | col->color;
+
+	perfstatrow_t * row;
+
+	int value;
+
+	if (hires)
+		draw_flags |= V_ALLOWLOWERCASE;
+
+	for (row = col->rows; row->lores_label; ++row)
 	{
-		if (vid.width < 640 || vid.height < 400) // low resolution
+		if (type == PERF_TIME)
+			value = I_PreciseToMicros(*(precise_t *)row->value);
+		else
+			value = *(int *)row->value;
+
+		if (hires)
 		{
-			snprintf(s, sizeof s - 1, "frmtime %d", frametime);
-			V_DrawThinString(20, 10, V_MONOSPACE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-			{
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 18, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 38, V_MONOSPACE | V_GRAYMAP, s);
-				return;
-			}
-			snprintf(s, sizeof s - 1, "drwtime %d", ps_rendercalltime);
-			V_DrawThinString(20, 18, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "bspcall %d", ps_numbspcalls);
-			V_DrawThinString(90, 10, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "sprites %d", ps_numsprites);
-			V_DrawThinString(90, 18, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "drwnode %d", ps_numdrawnodes);
-			V_DrawThinString(90, 26, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "plyobjs %d", ps_numpolyobjects);
-			V_DrawThinString(90, 34, V_MONOSPACE | V_BLUEMAP, s);
-#ifdef HWRENDER
-			if (rendermode == render_opengl) // OpenGL specific stats
-			{
-				snprintf(s, sizeof s - 1, "skybox  %d", ps_hw_skyboxtime);
-				V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "bsptime %d", ps_bsptime);
-				V_DrawThinString(24, 34, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "nodesrt %d", ps_hw_nodesorttime);
-				V_DrawThinString(24, 42, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "nodedrw %d", ps_hw_nodedrawtime);
-				V_DrawThinString(24, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprsort %d", ps_hw_spritesorttime);
-				V_DrawThinString(24, 58, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprdraw %d", ps_hw_spritedrawtime);
-				V_DrawThinString(24, 66, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "other   %d",
-					ps_rendercalltime - ps_hw_skyboxtime - ps_bsptime - ps_hw_nodesorttime
-					- ps_hw_nodedrawtime - ps_hw_spritesorttime - ps_hw_spritedrawtime
-					- ps_hw_batchsorttime - ps_hw_batchdrawtime);
-				V_DrawThinString(24, 74, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 82, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 90, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 102, V_MONOSPACE | V_GRAYMAP, s);
-				if (cv_glbatching.value)
-				{
-					snprintf(s, sizeof s - 1, "batsort %d", ps_hw_batchsorttime);
-					V_DrawThinString(90, 46, V_MONOSPACE | V_REDMAP, s);
-					snprintf(s, sizeof s - 1, "batdraw %d", ps_hw_batchdrawtime);
-					V_DrawThinString(90, 54, V_MONOSPACE | V_REDMAP, s);
-
-					snprintf(s, sizeof s - 1, "polygon %d", ps_hw_numpolys);
-					V_DrawThinString(155, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "drwcall %d", ps_hw_numcalls);
-					V_DrawThinString(155, 18, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "shaders %d", ps_hw_numshaders);
-					V_DrawThinString(155, 26, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "vertex  %d", ps_hw_numverts);
-					V_DrawThinString(155, 34, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "texture %d", ps_hw_numtextures);
-					V_DrawThinString(220, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "polyflg %d", ps_hw_numpolyflags);
-					V_DrawThinString(220, 18, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "colors  %d", ps_hw_numcolors);
-					V_DrawThinString(220, 26, V_MONOSPACE | V_PURPLEMAP, s);
-				}
-				else
-				{
-					// reset these vars so the "other" measurement isn't off
-					ps_hw_batchsorttime = 0;
-					ps_hw_batchdrawtime = 0;
-				}
-			}
-			else // software specific stats
-#endif
-			{
-				snprintf(s, sizeof s - 1, "bsptime %d", ps_bsptime);
-				V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprclip %d", ps_sw_spritecliptime);
-				V_DrawThinString(24, 34, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "portals %d", ps_sw_portaltime);
-				V_DrawThinString(24, 42, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "planes  %d", ps_sw_planetime);
-				V_DrawThinString(24, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "masked  %d", ps_sw_maskedtime);
-				V_DrawThinString(24, 58, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "other   %d",
-					ps_rendercalltime - ps_bsptime - ps_sw_spritecliptime
-					- ps_sw_portaltime - ps_sw_planetime - ps_sw_maskedtime);
-				V_DrawThinString(24, 66, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 74, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 82, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 94, V_MONOSPACE | V_GRAYMAP, s);
-			}
+			V_DrawSmallString(col->hires_x, draw_row, draw_flags,
+					va("%s %d", row->hires_label, value));
+
+			draw_row += 5;
 		}
-		else // high resolution
+		else
 		{
-			snprintf(s, sizeof s - 1, "Frame time:     %d", frametime);
-			V_DrawSmallString(20, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-			{
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-				return;
-			}
-			snprintf(s, sizeof s - 1, "3d rendering:   %d", ps_rendercalltime);
-			V_DrawSmallString(20, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "BSP calls:    %d", ps_numbspcalls);
-			V_DrawSmallString(115, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Sprites:      %d", ps_numsprites);
-			V_DrawSmallString(115, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Drawnodes:    %d", ps_numdrawnodes);
-			V_DrawSmallString(115, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Polyobjects:  %d", ps_numpolyobjects);
-			V_DrawSmallString(115, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-#ifdef HWRENDER
-			if (rendermode == render_opengl) // OpenGL specific stats
-			{
-				snprintf(s, sizeof s - 1, "Skybox render:  %d", ps_hw_skyboxtime);
-				V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "RenderBSPNode:  %d", ps_bsptime);
-				V_DrawSmallString(24, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Drwnode sort:   %d", ps_hw_nodesorttime);
-				V_DrawSmallString(24, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Drwnode render: %d", ps_hw_nodedrawtime);
-				V_DrawSmallString(24, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Sprite sort:    %d", ps_hw_spritesorttime);
-				V_DrawSmallString(24, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Sprite render:  %d", ps_hw_spritedrawtime);
-				V_DrawSmallString(24, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				// Remember to update this calculation when adding more 3d rendering stats!
-				snprintf(s, sizeof s - 1, "Other:          %d",
-					ps_rendercalltime - ps_hw_skyboxtime - ps_bsptime - ps_hw_nodesorttime
-					- ps_hw_nodedrawtime - ps_hw_spritesorttime - ps_hw_spritedrawtime
-					- ps_hw_batchsorttime - ps_hw_batchdrawtime);
-				V_DrawSmallString(24, 50, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 55, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 60, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 70, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-				if (cv_glbatching.value)
-				{
-					snprintf(s, sizeof s - 1, "Batch sort:   %d", ps_hw_batchsorttime);
-					V_DrawSmallString(115, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_REDMAP, s);
-					snprintf(s, sizeof s - 1, "Batch render: %d", ps_hw_batchdrawtime);
-					V_DrawSmallString(115, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_REDMAP, s);
-
-					snprintf(s, sizeof s - 1, "Polygons:   %d", ps_hw_numpolys);
-					V_DrawSmallString(200, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Vertices:   %d", ps_hw_numverts);
-					V_DrawSmallString(200, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Draw calls: %d", ps_hw_numcalls);
-					V_DrawSmallString(200, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Shaders:    %d", ps_hw_numshaders);
-					V_DrawSmallString(200, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Textures:   %d", ps_hw_numtextures);
-					V_DrawSmallString(200, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Polyflags:  %d", ps_hw_numpolyflags);
-					V_DrawSmallString(200, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Colors:     %d", ps_hw_numcolors);
-					V_DrawSmallString(200, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-				}
-				else
-				{
-					// reset these vars so the "other" measurement isn't off
-					ps_hw_batchsorttime = 0;
-					ps_hw_batchdrawtime = 0;
-				}
-			}
-			else // software specific stats
-#endif
-			{
-				snprintf(s, sizeof s - 1, "RenderBSPNode:  %d", ps_bsptime);
-				V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_ClipSprites:  %d", ps_sw_spritecliptime);
-				V_DrawSmallString(24, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Portals+Skybox: %d", ps_sw_portaltime);
-				V_DrawSmallString(24, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_DrawPlanes:   %d", ps_sw_planetime);
-				V_DrawSmallString(24, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_DrawMasked:   %d", ps_sw_maskedtime);
-				V_DrawSmallString(24, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				// Remember to update this calculation when adding more 3d rendering stats!
-				snprintf(s, sizeof s - 1, "Other:          %d",
-					ps_rendercalltime - ps_bsptime - ps_sw_spritecliptime
-					- ps_sw_portaltime - ps_sw_planetime - ps_sw_maskedtime);
-				V_DrawSmallString(24, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 50, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 55, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 65, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-			}
+			V_DrawThinString(col->lores_x, draw_row, draw_flags,
+					va("%s %d", row->lores_label, value));
+
+			draw_row += 8;
 		}
 	}
-	else if (cv_perfstats.value == 2) // logic
+}
+
+static void M_DrawPerfTiming(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_TIME);
+}
+
+static void M_DrawPerfCount(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_COUNT);
+}
+
+static void M_DrawRenderStats(void)
+{
+	const boolean hires = M_HighResolution();
+
+	const int half_row = hires ? 5 : 4;
+
+	precise_t extrarendertime;
+
+	perfstatrow_t frametime_row[] = {
+		{"frmtime", "Frame time:    ", &ps_frametime},
+		{0}
+	};
+
+	perfstatrow_t rendercalltime_row[] = {
+		{"drwtime", "3d rendering:  ", &ps_rendercalltime},
+		{0}
+	};
+
+	perfstatrow_t opengltime_row[] = {
+		{"skybox ", "Skybox render: ", &ps_hw_skyboxtime},
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"nodesrt", "Drwnode sort:  ", &ps_hw_nodesorttime},
+		{"nodedrw", "Drwnode render:", &ps_hw_nodedrawtime},
+		{"sprsort", "Sprite sort:   ", &ps_hw_spritesorttime},
+		{"sprdraw", "Sprite render: ", &ps_hw_spritedrawtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t softwaretime_row[] = {
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"sprclip", "R_ClipSprites: ", &ps_sw_spritecliptime},
+		{"portals", "Portals+Skybox:", &ps_sw_portaltime},
+		{"planes ", "R_DrawPlanes:  ", &ps_sw_planetime},
+		{"masked ", "R_DrawMasked:  ", &ps_sw_maskedtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t uiswaptime_row[] = {
+		{"ui     ", "UI render:     ", &ps_uitime},
+		{"finupdt", "I_FinishUpdate:", &ps_swaptime},
+		{0}
+	};
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:    ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t rendercalls_row[] = {
+		{"bspcall", "BSP calls:   ", &ps_numbspcalls},
+		{"sprites", "Sprites:     ", &ps_numsprites},
+		{"drwnode", "Drawnodes:   ", &ps_numdrawnodes},
+		{"plyobjs", "Polyobjects: ", &ps_numpolyobjects},
+		{0}
+	};
+
+	perfstatrow_t batchtime_row[] = {
+		{"batsort", "Batch sort:  ", &ps_hw_batchsorttime},
+		{"batdraw", "Batch render:", &ps_hw_batchdrawtime},
+		{0}
+	};
+
+	perfstatrow_t batchcount_row[] = {
+		{"polygon", "Polygons:  ", &ps_hw_numpolys},
+		{"vertex ", "Vertices:  ", &ps_hw_numverts},
+		{0}
+	};
+
+	perfstatrow_t batchcalls_row[] = {
+		{"drwcall", "Draw calls:", &ps_hw_numcalls},
+		{"shaders", "Shaders:   ", &ps_hw_numshaders},
+		{"texture", "Textures:  ", &ps_hw_numtextures},
+		{"polyflg", "Polyflags: ", &ps_hw_numpolyflags},
+		{"colors ", "Colors:    ", &ps_hw_numcolors},
+		{0}
+	};
+
+	perfstatcol_t      frametime_col =  {20,  20, V_YELLOWMAP,      frametime_row};
+	perfstatcol_t rendercalltime_col =  {20,  20, V_YELLOWMAP, rendercalltime_row};
+
+	perfstatcol_t     opengltime_col =  {24,  24, V_YELLOWMAP,     opengltime_row};
+	perfstatcol_t   softwaretime_col =  {24,  24, V_YELLOWMAP,   softwaretime_row};
+
+	perfstatcol_t     uiswaptime_col =  {20,  20, V_YELLOWMAP,     uiswaptime_row};
+	perfstatcol_t        tictime_col =  {20,  20, V_GRAYMAP,          tictime_row};
+
+	perfstatcol_t    rendercalls_col =  {90, 115, V_BLUEMAP,      rendercalls_row};
+
+	perfstatcol_t      batchtime_col =  {90, 115, V_REDMAP,         batchtime_row};
+
+	perfstatcol_t     batchcount_col = {155, 200, V_PURPLEMAP,     batchcount_row};
+	perfstatcol_t     batchcalls_col = {220, 200, V_PURPLEMAP,     batchcalls_row};
+
+
+	boolean rendering = (
+			gamestate == GS_LEVEL ||
+			(gamestate == GS_TITLESCREEN && titlemapinaction)
+	);
+
+	draw_row = 10;
+	M_DrawPerfTiming(&frametime_col);
+
+	if (rendering)
 	{
-		int i = 0;
-		thinker_t *thinker;
-		int thinkercount = 0;
-		int polythcount = 0;
-		int mainthcount = 0;
-		int mobjcount = 0;
-		int nothinkcount = 0;
-		int scenerycount = 0;
-		int dynslopethcount = 0;
-		int precipcount = 0;
-		int removecount = 0;
-		// y offset for drawing columns
-		int yoffset1 = 0;
-		int yoffset2 = 0;
-
-		for (i = 0; i < NUM_THINKERLISTS; i++)
+		M_DrawPerfTiming(&rendercalltime_col);
+
+		// Remember to update this calculation when adding more 3d rendering stats!
+		extrarendertime = ps_rendercalltime - ps_bsptime;
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
 		{
-			for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
+			extrarendertime -=
+				ps_hw_skyboxtime +
+				ps_hw_nodesorttime +
+				ps_hw_nodedrawtime +
+				ps_hw_spritesorttime +
+				ps_hw_spritedrawtime;
+
+			if (cv_glbatching.value)
 			{
-				thinkercount++;
-				if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-					removecount++;
-				else if (i == THINK_POLYOBJ)
-					polythcount++;
-				else if (i == THINK_MAIN)
-					mainthcount++;
-				else if (i == THINK_MOBJ)
-				{
-					if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
-					{
-						mobj_t *mobj = (mobj_t*)thinker;
-						mobjcount++;
-						if (mobj->flags & MF_NOTHINK)
-							nothinkcount++;
-						else if (mobj->flags & MF_SCENERY)
-							scenerycount++;
-					}
-				}
-				else if (i == THINK_DYNSLOPE)
-					dynslopethcount++;
-				else if (i == THINK_PRECIP)
-					precipcount++;
+				extrarendertime -=
+					ps_hw_batchsorttime +
+					ps_hw_batchdrawtime;
 			}
+
+			M_DrawPerfTiming(&opengltime_col);
+		}
+		else
+#endif
+		{
+			extrarendertime -=
+				ps_sw_spritecliptime +
+				ps_sw_portaltime +
+				ps_sw_planetime +
+				ps_sw_maskedtime;
+
+			M_DrawPerfTiming(&softwaretime_col);
 		}
+	}
 
-		if (vid.width < 640 || vid.height < 400) // low resolution
+	M_DrawPerfTiming(&uiswaptime_col);
+
+	draw_row += half_row;
+	M_DrawPerfTiming(&tictime_col);
+
+	if (rendering)
+	{
+		draw_row = 10;
+		M_DrawPerfCount(&rendercalls_col);
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl && cv_glbatching.value)
 		{
-			snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-			V_DrawThinString(20, 10, V_MONOSPACE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-				return;
-			snprintf(s, sizeof s - 1, "plrthnk %d", ps_playerthink_time);
-			V_DrawThinString(24, 18, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "thnkers %d", ps_thinkertime);
-			V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-			for (i = 0; i < NUM_THINKERLISTS; i++)
-			{
-				yoffset1 += 8;
-				snprintf(s, sizeof s - 1, thlist_shortnames[i], ps_thlist_times[i]);
-				V_DrawThinString(28, 26+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-			}
-			snprintf(s, sizeof s - 1, "lthinkf %d", ps_lua_thinkframe_time);
-			V_DrawThinString(24, 34+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "other   %d",
-				ps_tictime - ps_playerthink_time - ps_thinkertime - ps_lua_thinkframe_time);
-			V_DrawThinString(24, 42+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-
-			snprintf(s, sizeof s - 1, "thnkers %d", thinkercount);
-			V_DrawThinString(90, 10, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "plyobjs %d", polythcount);
-			V_DrawThinString(94, 18, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "main    %d", mainthcount);
-			V_DrawThinString(94, 26, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "mobjs   %d", mobjcount);
-			V_DrawThinString(94, 34, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "regular %d", mobjcount - scenerycount - nothinkcount);
-			V_DrawThinString(98, 42, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "scenery %d", scenerycount);
-			V_DrawThinString(98, 50, V_MONOSPACE | V_BLUEMAP, s);
-			if (nothinkcount)
-			{
-				snprintf(s, sizeof s - 1, "nothink %d", nothinkcount);
-				V_DrawThinString(98, 58, V_MONOSPACE | V_BLUEMAP, s);
-				yoffset2 += 8;
-			}
-			snprintf(s, sizeof s - 1, "dynslop %d", dynslopethcount);
-			V_DrawThinString(94, 58+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "precip  %d", precipcount);
-			V_DrawThinString(94, 66+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "remove  %d", removecount);
-			V_DrawThinString(94, 74+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-
-			snprintf(s, sizeof s - 1, "lmhooks %d", ps_lua_mobjhooks);
-			V_DrawThinString(170, 10, V_MONOSPACE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "chkpos  %d", ps_checkposition_calls);
-			V_DrawThinString(170, 18, V_MONOSPACE | V_PURPLEMAP, s);
+			draw_row += half_row;
+			M_DrawPerfTiming(&batchtime_col);
+
+			draw_row = 10;
+			M_DrawPerfCount(&batchcount_col);
+
+			if (hires)
+				draw_row += half_row;
+			else
+				draw_row  = 10;
+
+			M_DrawPerfCount(&batchcalls_col);
 		}
-		else // high resolution
+#endif
+	}
+}
+
+static void M_DrawTickStats(void)
+{
+	int i = 0;
+	thinker_t *thinker;
+	int thinkercount = 0;
+	int polythcount = 0;
+	int mainthcount = 0;
+	int mobjcount = 0;
+	int nothinkcount = 0;
+	int scenerycount = 0;
+	int regularcount = 0;
+	int dynslopethcount = 0;
+	int precipcount = 0;
+	int removecount = 0;
+
+	precise_t extratime =
+		ps_tictime -
+		ps_playerthink_time -
+		ps_thinkertime -
+		ps_lua_thinkframe_time;
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:     ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t thinker_time_row[] = {
+		{"plrthnk", "P_PlayerThink:  ", &ps_playerthink_time},
+		{"thnkers", "P_RunThinkers:  ", &ps_thinkertime},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinker_time_row[] = {
+		{"plyobjs", "Polyobjects:    ", &ps_thlist_times[THINK_POLYOBJ]},
+		{"main   ", "Main:           ", &ps_thlist_times[THINK_MAIN]},
+		{"mobjs  ", "Mobjs:          ", &ps_thlist_times[THINK_MOBJ]},
+		{"dynslop", "Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE]},
+		{"precip ", "Precipitation:  ", &ps_thlist_times[THINK_PRECIP]},
+		{0}
+	};
+
+	perfstatrow_t extra_thinker_time_row[] = {
+		{"lthinkf", "LUAh_ThinkFrame:", &ps_lua_thinkframe_time},
+		{"other  ", "Other:          ", &extratime},
+		{0}
+	};
+
+	perfstatrow_t thinkercount_row[] = {
+		{"thnkers", "Thinkers:       ", &thinkercount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row[] = {
+		{"plyobjs", "Polyobjects:    ", &polythcount},
+		{"main   ", "Main:           ", &mainthcount},
+		{"mobjs  ", "Mobjs:          ", &mobjcount},
+		{0}
+	};
+
+	perfstatrow_t mobjthinkercount_row[] = {
+		{"regular", "Regular:        ", &regularcount},
+		{"scenery", "Scenery:        ", &scenerycount},
+		{0}
+	};
+
+	perfstatrow_t nothinkcount_row[] = {
+		{"nothink", "Nothink:        ", &nothinkcount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row2[] = {
+		{"dynslop", "Dynamic slopes: ", &dynslopethcount},
+		{"precip ", "Precipitation:  ", &precipcount},
+		{"remove ", "Pending removal:", &removecount},
+		{0}
+	};
+
+	perfstatrow_t misc_calls_row[] = {
+		{"lmhook", "Lua mobj hooks: ", &ps_lua_mobjhooks},
+		{"chkpos", "P_CheckPosition:", &ps_checkposition_calls},
+		{0}
+	};
+
+	perfstatcol_t               tictime_col  =  {20,  20, V_YELLOWMAP,               tictime_row};
+	perfstatcol_t          thinker_time_col  =  {24,  24, V_YELLOWMAP,          thinker_time_row};
+	perfstatcol_t detailed_thinker_time_col  =  {28,  28, V_YELLOWMAP, detailed_thinker_time_row};
+	perfstatcol_t    extra_thinker_time_col  =  {24,  24, V_YELLOWMAP,    extra_thinker_time_row};
+
+	perfstatcol_t          thinkercount_col  =  {90, 115, V_BLUEMAP,            thinkercount_row};
+	perfstatcol_t detailed_thinkercount_col  =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row};
+	perfstatcol_t      mobjthinkercount_col  =  {98, 123, V_BLUEMAP,        mobjthinkercount_row};
+	perfstatcol_t          nothinkcount_col  =  {98, 123, V_BLUEMAP,            nothinkcount_row};
+	perfstatcol_t detailed_thinkercount_col2 =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row2};
+	perfstatcol_t            misc_calls_col  = {170, 216, V_PURPLEMAP,            misc_calls_row};
+
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+	{
+		for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
 		{
-			snprintf(s, sizeof s - 1, "Game logic:      %d", ps_tictime);
-			V_DrawSmallString(20, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-				return;
-			snprintf(s, sizeof s - 1, "P_PlayerThink:   %d", ps_playerthink_time);
-			V_DrawSmallString(24, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "P_RunThinkers:   %d", ps_thinkertime);
-			V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			for (i = 0; i < NUM_THINKERLISTS; i++)
+			thinkercount++;
+			if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				removecount++;
+			else if (i == THINK_POLYOBJ)
+				polythcount++;
+			else if (i == THINK_MAIN)
+				mainthcount++;
+			else if (i == THINK_MOBJ)
 			{
-				yoffset1 += 5;
-				snprintf(s, sizeof s - 1, thlist_names[i], ps_thlist_times[i]);
-				V_DrawSmallString(28, 20+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			}
-			snprintf(s, sizeof s - 1, "LUAh_ThinkFrame: %d", ps_lua_thinkframe_time);
-			V_DrawSmallString(24, 25+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "Other:           %d",
-				ps_tictime - ps_playerthink_time - ps_thinkertime - ps_lua_thinkframe_time);
-			V_DrawSmallString(24, 30+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-
-			snprintf(s, sizeof s - 1, "Thinkers:        %d", thinkercount);
-			V_DrawSmallString(115, 10+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Polyobjects:     %d", polythcount);
-			V_DrawSmallString(119, 15+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Main:            %d", mainthcount);
-			V_DrawSmallString(119, 20+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Mobjs:           %d", mobjcount);
-			V_DrawSmallString(119, 25+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Regular:         %d", mobjcount - scenerycount - nothinkcount);
-			V_DrawSmallString(123, 30+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Scenery:         %d", scenerycount);
-			V_DrawSmallString(123, 35+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			if (nothinkcount)
-			{
-				snprintf(s, sizeof s - 1, "Nothink:         %d", nothinkcount);
-				V_DrawSmallString(123, 40+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-				yoffset2 += 5;
+				if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
+				{
+					mobj_t *mobj = (mobj_t*)thinker;
+					mobjcount++;
+					if (mobj->flags & MF_NOTHINK)
+						nothinkcount++;
+					else if (mobj->flags & MF_SCENERY)
+						scenerycount++;
+					else
+						regularcount++;
+				}
 			}
-			snprintf(s, sizeof s - 1, "Dynamic slopes:  %d", dynslopethcount);
-			V_DrawSmallString(119, 40+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Precipitation:   %d", precipcount);
-			V_DrawSmallString(119, 45+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Pending removal: %d", removecount);
-			V_DrawSmallString(119, 50+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-
-			snprintf(s, sizeof s - 1, "Calls:");
-			V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "Lua mobj hooks:  %d", ps_lua_mobjhooks);
-			V_DrawSmallString(216, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "P_CheckPosition: %d", ps_checkposition_calls);
-			V_DrawSmallString(216, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
+			else if (i == THINK_DYNSLOPE)
+				dynslopethcount++;
+			else if (i == THINK_PRECIP)
+				precipcount++;
 		}
 	}
+
+	draw_row = 10;
+	M_DrawPerfTiming(&tictime_col);
+	M_DrawPerfTiming(&thinker_time_col);
+	M_DrawPerfTiming(&detailed_thinker_time_col);
+	M_DrawPerfTiming(&extra_thinker_time_col);
+
+	draw_row = 10;
+	M_DrawPerfCount(&thinkercount_col);
+	M_DrawPerfCount(&detailed_thinkercount_col);
+	M_DrawPerfCount(&mobjthinkercount_col);
+
+	if (nothinkcount)
+		M_DrawPerfCount(&nothinkcount_col);
+
+	M_DrawPerfCount(&detailed_thinkercount_col2);
+
+	if (M_HighResolution())
+	{
+		V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, "Calls:");
+
+		draw_row = 15;
+	}
+	else
+	{
+		draw_row = 10;
+	}
+
+	M_DrawPerfCount(&misc_calls_col);
+}
+
+void M_DrawPerfStats(void)
+{
+	char s[100];
+
+	PS_SetFrameTime();
+
+	if (cv_perfstats.value == 1) // rendering
+	{
+		M_DrawRenderStats();
+	}
+	else if (cv_perfstats.value == 2) // logic
+	{
+		M_DrawTickStats();
+	}
 	else if (cv_perfstats.value == 3) // lua thinkframe
 	{
 		if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
diff --git a/src/m_perfstats.h b/src/m_perfstats.h
index 01a818c1c360c3cdc6231069686edcb7bf3544ee..132bea38c696acaf9a7093cc81f795918452356d 100644
--- a/src/m_perfstats.h
+++ b/src/m_perfstats.h
@@ -16,17 +16,17 @@
 #include "lua_script.h"
 #include "p_local.h"
 
-extern int ps_tictime;
+extern precise_t ps_tictime;
 
-extern int ps_playerthink_time;
-extern int ps_thinkertime;
+extern precise_t ps_playerthink_time;
+extern precise_t ps_thinkertime;
 
-extern int ps_thlist_times[];
+extern precise_t ps_thlist_times[];
 
-extern int ps_checkposition_calls;
+extern int       ps_checkposition_calls;
 
-extern int ps_lua_thinkframe_time;
-extern int ps_lua_mobjhooks;
+extern precise_t ps_lua_thinkframe_time;
+extern int       ps_lua_mobjhooks;
 
 typedef struct
 {
diff --git a/src/p_tick.c b/src/p_tick.c
index 451e5e6266d9084586e21f0211e9a4ff3b15210d..19fdc221bedae8588f1453e934e230eef237d9f0 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -22,7 +22,7 @@
 #include "lua_script.h"
 #include "lua_hook.h"
 #include "m_perfstats.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 // Object place
 #include "m_cheat.h"
@@ -323,7 +323,7 @@ static inline void P_RunThinkers(void)
 	size_t i;
 	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		ps_thlist_times[i] = I_GetTimeMicros();
+		ps_thlist_times[i] = I_GetPreciseTime();
 		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = currentthinker->next)
 		{
 #ifdef PARANOIA
@@ -331,7 +331,7 @@ static inline void P_RunThinkers(void)
 #endif
 			currentthinker->function.acp1(currentthinker);
 		}
-		ps_thlist_times[i] = I_GetTimeMicros() - ps_thlist_times[i];
+		ps_thlist_times[i] = I_GetPreciseTime() - ps_thlist_times[i];
 	}
 
 }
@@ -650,11 +650,11 @@ void P_Ticker(boolean run)
 
 		LUAh_PreThinkFrame();
 
-		ps_playerthink_time = I_GetTimeMicros();
+		ps_playerthink_time = I_GetPreciseTime();
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 				P_PlayerThink(&players[i]);
-		ps_playerthink_time = I_GetTimeMicros() - ps_playerthink_time;
+		ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
 	}
 
 	// Keep track of how long they've been playing!
@@ -669,18 +669,18 @@ void P_Ticker(boolean run)
 
 	if (run)
 	{
-		ps_thinkertime = I_GetTimeMicros();
+		ps_thinkertime = I_GetPreciseTime();
 		P_RunThinkers();
-		ps_thinkertime = I_GetTimeMicros() - ps_thinkertime;
+		ps_thinkertime = I_GetPreciseTime() - ps_thinkertime;
 
 		// Run any "after all the other thinkers" stuff
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 				P_PlayerAfterThink(&players[i]);
 
-		ps_lua_thinkframe_time = I_GetTimeMicros();
+		ps_lua_thinkframe_time = I_GetPreciseTime();
 		LUAh_ThinkFrame();
-		ps_lua_thinkframe_time = I_GetTimeMicros() - ps_lua_thinkframe_time;
+		ps_lua_thinkframe_time = I_GetPreciseTime() - ps_lua_thinkframe_time;
 	}
 
 	// Run shield positioning
diff --git a/src/r_main.c b/src/r_main.c
index 5165b3c87b3fa51a1505fb9033715c2f713d6a46..7f2dd67290d5604e6d30f798db1311ec8f51531e 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -34,7 +34,7 @@
 #include "m_random.h" // quake camera shake
 #include "r_portal.h"
 #include "r_main.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -100,17 +100,17 @@ lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 extracolormap_t *extra_colormaps = NULL;
 
 // Render stats
-int ps_prevframetime = 0;
-int ps_rendercalltime = 0;
-int ps_uitime = 0;
-int ps_swaptime = 0;
+precise_t ps_prevframetime = 0;
+precise_t ps_rendercalltime = 0;
+precise_t ps_uitime = 0;
+precise_t ps_swaptime = 0;
 
-int ps_bsptime = 0;
+precise_t ps_bsptime = 0;
 
-int ps_sw_spritecliptime = 0;
-int ps_sw_portaltime = 0;
-int ps_sw_planetime = 0;
-int ps_sw_maskedtime = 0;
+precise_t ps_sw_spritecliptime = 0;
+precise_t ps_sw_portaltime = 0;
+precise_t ps_sw_planetime = 0;
+precise_t ps_sw_maskedtime = 0;
 
 int ps_numbspcalls = 0;
 int ps_numsprites = 0;
@@ -1491,9 +1491,9 @@ void R_RenderPlayerView(player_t *player)
 	ProfZeroTimer();
 #endif
 	ps_numbspcalls = ps_numpolyobjects = ps_numdrawnodes = 0;
-	ps_bsptime = I_GetTimeMicros();
+	ps_bsptime = I_GetPreciseTime();
 	R_RenderBSPNode((INT32)numnodes - 1);
-	ps_bsptime = I_GetTimeMicros() - ps_bsptime;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
 	ps_numsprites = visspritecount;
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
@@ -1504,9 +1504,9 @@ void R_RenderPlayerView(player_t *player)
 //profile stuff ---------------------------------------------------------
 	Mask_Post(&masks[nummasks - 1]);
 
-	ps_sw_spritecliptime = I_GetTimeMicros();
+	ps_sw_spritecliptime = I_GetPreciseTime();
 	R_ClipSprites(drawsegs, NULL);
-	ps_sw_spritecliptime = I_GetTimeMicros() - ps_sw_spritecliptime;
+	ps_sw_spritecliptime = I_GetPreciseTime() - ps_sw_spritecliptime;
 
 
 	// Add skybox portals caused by sky visplanes.
@@ -1514,7 +1514,7 @@ void R_RenderPlayerView(player_t *player)
 		Portal_AddSkyboxPortals();
 
 	// Portal rendering. Hijacks the BSP traversal.
-	ps_sw_portaltime = I_GetTimeMicros();
+	ps_sw_portaltime = I_GetPreciseTime();
 	if (portal_base)
 	{
 		portal_t *portal;
@@ -1554,20 +1554,20 @@ void R_RenderPlayerView(player_t *player)
 			Portal_Remove(portal);
 		}
 	}
-	ps_sw_portaltime = I_GetTimeMicros() - ps_sw_portaltime;
+	ps_sw_portaltime = I_GetPreciseTime() - ps_sw_portaltime;
 
-	ps_sw_planetime = I_GetTimeMicros();
+	ps_sw_planetime = I_GetPreciseTime();
 	R_DrawPlanes();
 #ifdef FLOORSPLATS
 	R_DrawVisibleFloorSplats();
 #endif
-	ps_sw_planetime = I_GetTimeMicros() - ps_sw_planetime;
+	ps_sw_planetime = I_GetPreciseTime() - ps_sw_planetime;
 
 	// draw mid texture and sprite
 	// And now 3D floors/sides!
-	ps_sw_maskedtime = I_GetTimeMicros();
+	ps_sw_maskedtime = I_GetPreciseTime();
 	R_DrawMasked(masks, nummasks);
-	ps_sw_maskedtime = I_GetTimeMicros() - ps_sw_maskedtime;
+	ps_sw_maskedtime = I_GetPreciseTime() - ps_sw_maskedtime;
 
 	free(masks);
 }
diff --git a/src/r_main.h b/src/r_main.h
index 379b5b8df1f5d306e50b2eed802c058749cb3928..eb3d0eeddfd9063564fc8364ebc02367d325b14d 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -78,17 +78,17 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 
 // Render stats
 
-extern int ps_prevframetime;// time when previous frame was rendered
-extern int ps_rendercalltime;
-extern int ps_uitime;
-extern int ps_swaptime;
+extern precise_t ps_prevframetime;// time when previous frame was rendered
+extern precise_t ps_rendercalltime;
+extern precise_t ps_uitime;
+extern precise_t ps_swaptime;
 
-extern int ps_bsptime;
+extern precise_t ps_bsptime;
 
-extern int ps_sw_spritecliptime;
-extern int ps_sw_portaltime;
-extern int ps_sw_planetime;
-extern int ps_sw_maskedtime;
+extern precise_t ps_sw_spritecliptime;
+extern precise_t ps_sw_portaltime;
+extern precise_t ps_sw_planetime;
+extern precise_t ps_sw_maskedtime;
 
 extern int ps_numbspcalls;
 extern int ps_numsprites;