diff --git a/src/d_main.c b/src/d_main.c
index c0a8eacec4cc6c49ba521a74a03c0358aee00768..5631cb939f87060036fe82335a0c3277503adc1c 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -411,6 +411,7 @@ static void D_Display(void)
 
 			if (!automapactive && !dedicated && cv_renderview.value)
 			{
+				rs_rendercalltime = I_GetTimeMicros();
 				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
 				{
 					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
@@ -457,6 +458,7 @@ static void D_Display(void)
 					if (postimgtype2)
 						V_DoPostProcessor(1, postimgtype2, postimgparam2);
 				}
+				rs_rendercalltime = I_GetTimeMicros() - rs_rendercalltime;
 			}
 
 			if (lastdraw)
@@ -591,8 +593,59 @@ static void D_Display(void)
 			snprintf(s, sizeof s - 1, "SysMiss %.2f%%", lostpercent);
 			V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s);
 		}
+		
+		if (cv_renderstats.value)
+		{
+			char s[50];
+			int frametime = I_GetTimeMicros() - rs_prevframetime;
+			int divisor = 1;
+			rs_prevframetime = I_GetTimeMicros();
+
+			if (rs_rendercalltime > 10000) divisor = 1000;
+			
+			snprintf(s, sizeof s - 1, "ft   %d", frametime / divisor);
+			V_DrawThinString(30, 10, V_MONOSPACE | V_YELLOWMAP, s);
+			snprintf(s, sizeof s - 1, "rtot %d", rs_rendercalltime / divisor);
+			V_DrawThinString(30, 20, V_MONOSPACE | V_YELLOWMAP, s);
+			snprintf(s, sizeof s - 1, "bsp  %d", rs_bsptime / divisor);
+			V_DrawThinString(30, 30, V_MONOSPACE | V_YELLOWMAP, s);
+			snprintf(s, sizeof s - 1, "nbsp %d", rs_numbspcalls);
+			V_DrawThinString(80, 10, V_MONOSPACE | V_BLUEMAP, s);
+			snprintf(s, sizeof s - 1, "nspr %d", rs_numsprites);
+			V_DrawThinString(80, 20, V_MONOSPACE | V_BLUEMAP, s);
+			snprintf(s, sizeof s - 1, "nnod %d", rs_numdrawnodes);
+			V_DrawThinString(80, 30, V_MONOSPACE | V_BLUEMAP, s);
+			snprintf(s, sizeof s - 1, "npob %d", rs_numpolyobjects);
+			V_DrawThinString(80, 40, V_MONOSPACE | V_BLUEMAP, s);
+			if (rendermode == render_opengl) // OpenGL specific stats
+			{
+				snprintf(s, sizeof s - 1, "nsrt %d", rs_hw_nodesorttime / divisor);
+				V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "ndrw %d", rs_hw_nodedrawtime / divisor);
+				V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "ssrt %d", rs_hw_spritesorttime / divisor);
+				V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "sdrw %d", rs_hw_spritedrawtime / divisor);
+				V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "fin  %d", rs_swaptime / divisor);
+				V_DrawThinString(30, 80, V_MONOSPACE | V_YELLOWMAP, s);
+			}
+			else // software specific stats
+			{
+				snprintf(s, sizeof s - 1, "prtl %d", rs_sw_portaltime / divisor);
+				V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "plns %d", rs_sw_planetime / divisor);
+				V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "mskd %d", rs_sw_maskedtime / divisor);
+				V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
+				snprintf(s, sizeof s - 1, "fin  %d", rs_swaptime / divisor);
+				V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
+			}
+		}
 
+		rs_swaptime = I_GetTimeMicros();
 		I_FinishUpdate(); // page flip or blit buffer
+		rs_swaptime = I_GetTimeMicros() - rs_swaptime;
 	}
 
 	needpatchflush = false;
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index 5c0f7eb99ed6ab0d41fc93bcfd87fc89ab8d044b..4a657ed19db3cb0166c10c3d9f38bd28a54b139f 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -16,6 +16,11 @@ tic_t I_GetTime(void)
 	return 0;
 }
 
+int I_GetTimeMicros(void)
+{
+	return 0;
+}
+
 void I_Sleep(void){}
 
 void I_GetEvent(void){}
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 7f640553fe287c3b5b798dff80b630af0ddd9a77..07a00d7e7037e59eb2c6afed143ff670bc67a8a4 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -146,6 +146,13 @@ static float gr_fovlud;
 static angle_t gr_aimingangle;
 static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
 
+// Render stats
+int rs_hw_nodesorttime = 0;
+int rs_hw_nodedrawtime = 0;
+int rs_hw_spritesorttime = 0;
+int rs_hw_spritedrawtime = 0;
+
+
 // ==========================================================================
 // Lighting
 // ==========================================================================
@@ -3463,6 +3470,9 @@ static void HWR_Subsector(size_t num)
 			po = (polyobj_t *)(po->link.next);
 		}
 
+		// for render stats
+		rs_numpolyobjects += numpolys;
+
 		// Sort polyobjects
 		R_SortPolyObjects(sub);
 
@@ -3577,6 +3587,8 @@ static void HWR_RenderBSPNode(INT32 bspnum)
 
 	// Decide which side the view point is on
 	INT32 side;
+	
+	rs_numbspcalls++;
 
 	// Found a subsector?
 	if (bspnum & NF_SUBSECTOR)
@@ -4751,6 +4763,8 @@ static void HWR_CreateDrawNodes(void)
 
 	// If true, swap the draw order.
 	boolean shift = false;
+	
+	rs_hw_nodesorttime = I_GetTimeMicros();
 
 	for (i = 0; i < numplanes; i++, p++)
 	{
@@ -4769,6 +4783,8 @@ static void HWR_CreateDrawNodes(void)
 		sortnode[p].wall = &wallinfo[i];
 		sortindex[p] = p;
 	}
+	
+	rs_numdrawnodes = p;
 
 	// p is the number of stuff to sort
 
@@ -4871,6 +4887,10 @@ static void HWR_CreateDrawNodes(void)
 		} //i++
 	} // loop++
 
+	rs_hw_nodesorttime = I_GetTimeMicros() - rs_hw_nodesorttime;
+	
+	rs_hw_nodedrawtime = I_GetTimeMicros();
+
 	// Okay! Let's draw it all! Woo!
 	HWD.pfnSetTransform(&atransform);
 	HWD.pfnSetShader(0);
@@ -4905,6 +4925,8 @@ static void HWR_CreateDrawNodes(void)
 				sortnode[sortindex[i]].wall->lightlevel, sortnode[sortindex[i]].wall->wallcolormap);
 		}
 	}
+	
+	rs_hw_nodedrawtime = I_GetTimeMicros() - rs_hw_nodedrawtime;
 
 	numwalls = 0;
 	numplanes = 0;
@@ -5981,6 +6003,10 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnSetSpecialState(HWD_SET_SHADERS, cv_grshaders.value);
 	HWD.pfnSetShader(0);
 
+	rs_numbspcalls = 0;
+	rs_numpolyobjects = 0;
+	rs_bsptime = I_GetTimeMicros();
+
 	validcount++;
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
@@ -6014,6 +6040,8 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 #endif
 
+	rs_bsptime = I_GetTimeMicros() - rs_bsptime;
+
 	// Check for new console commands.
 	NetUpdate();
 
@@ -6024,14 +6052,22 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 #endif
 
 	// Draw MD2 and sprites
+	rs_numsprites = gr_visspritecount;
+	rs_hw_spritesorttime = I_GetTimeMicros();
 	HWR_SortVisSprites();
+	rs_hw_spritesorttime = I_GetTimeMicros() - rs_hw_spritesorttime;
+	rs_hw_spritedrawtime = I_GetTimeMicros();
 	HWR_DrawSprites();
+	rs_hw_spritedrawtime = I_GetTimeMicros() - rs_hw_spritedrawtime;
 
 #ifdef NEWCORONAS
 	//Hurdler: they must be drawn before translucent planes, what about gl fog?
 	HWR_DrawCoronas();
 #endif
 
+	rs_numdrawnodes = 0;
+	rs_hw_nodesorttime = 0;
+	rs_hw_nodedrawtime = 0;
 	if (numplanes || numpolyplanes || numwalls) //Hurdler: render 3D water and transparent walls after everything
 	{
 		HWR_CreateDrawNodes();
@@ -6134,6 +6170,8 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_grfiltermode);
 	CV_RegisterVar(&cv_grcorrecttricks);
 	CV_RegisterVar(&cv_grsolvetjoin);
+	
+	CV_RegisterVar(&cv_renderstats);
 
 #ifndef NEWCLIP
 	CV_RegisterVar(&cv_grclipwalls);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index aedfa9cd6206face0d2f2c210093ad5e96bc2cd5..fc49364da17190064c0d265296fda3eca4264494 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -104,4 +104,11 @@ extern float gr_viewwindowx, gr_basewindowcentery;
 extern fixed_t *hwbbox;
 extern FTransform atransform;
 
+
+// Render stats
+extern int rs_hw_nodesorttime;
+extern int rs_hw_nodedrawtime;
+extern int rs_hw_spritesorttime;
+extern int rs_hw_spritedrawtime;
+
 #endif
diff --git a/src/i_system.h b/src/i_system.h
index b38748244b9465107686f8d3ba74cebb771db418..dd0b65f6df542d228019f5c3c91d59c5bcd6728c 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -46,6 +46,8 @@ UINT32 I_GetFreeMem(UINT32 *total);
 */
 tic_t I_GetTime(void);
 
+int I_GetTimeMicros(void);// provides microsecond counter for render stats
+
 /**	\brief	The I_Sleep function
 
 	\return	void
diff --git a/src/r_bsp.c b/src/r_bsp.c
index c0011f4b9608ced5025bd4ceee912add40114301..355d55bc686ed20155acbec19f6d2737420ab625 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -812,6 +812,9 @@ static void R_AddPolyObjects(subsector_t *sub)
 		po = (polyobj_t *)(po->link.next);
 	}
 
+	// for render stats
+	rs_numpolyobjects += numpolys;
+
 	// sort polyobjects
 	R_SortPolyObjects(sub);
 
@@ -1363,6 +1366,9 @@ void R_RenderBSPNode(INT32 bspnum)
 {
 	node_t *bsp;
 	INT32 side;
+
+	rs_numbspcalls++;
+
 	while (!(bspnum & NF_SUBSECTOR))  // Found a subsector?
 	{
 		bsp = &nodes[bspnum];
diff --git a/src/r_main.c b/src/r_main.c
index a881e046d2e3978bd5432a96a7ea5d9253f87d29..4d1be4b1452ecada3a7dbc44fd9ceba2e1b74d9d 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -34,6 +34,7 @@
 #include "m_random.h" // quake camera shake
 #include "r_portal.h"
 #include "r_main.h"
+#include "i_system.h" // I_GetTimeMicros
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -98,6 +99,22 @@ lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 // Hack to support extra boom colormaps.
 extracolormap_t *extra_colormaps = NULL;
 
+// Render stats
+int rs_prevframetime = 0;
+int rs_rendercalltime = 0;
+int rs_swaptime = 0;
+
+int rs_bsptime = 0;
+
+int rs_sw_portaltime = 0;
+int rs_sw_planetime = 0;
+int rs_sw_maskedtime = 0;
+
+int rs_numbspcalls = 0;
+int rs_numsprites = 0;
+int rs_numdrawnodes = 0;
+int rs_numpolyobjects = 0;
+
 static CV_PossibleValue_t drawdist_cons_t[] = {
 	{256, "256"},	{512, "512"},	{768, "768"},
 	{1024, "1024"},	{1536, "1536"},	{2048, "2048"},
@@ -148,6 +165,8 @@ consvar_t cv_homremoval = {"homremoval", "No", CV_SAVE, homremoval_cons_t, NULL,
 
 consvar_t cv_maxportals = {"maxportals", "2", CV_SAVE, maxportals_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_renderstats = {"renderstats", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 void SplitScreen_OnChange(void)
 {
 	if (!cv_debug && netgame)
@@ -1445,7 +1464,11 @@ void R_RenderPlayerView(player_t *player)
 	mytotal = 0;
 	ProfZeroTimer();
 #endif
+	rs_numbspcalls = rs_numpolyobjects = rs_numdrawnodes = 0;
+	rs_bsptime = I_GetTimeMicros();
 	R_RenderBSPNode((INT32)numnodes - 1);
+	rs_bsptime = I_GetTimeMicros() - rs_bsptime;
+	rs_numsprites = visspritecount;
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
 	mytotal += mycount; // 64bit add
@@ -1463,6 +1486,7 @@ void R_RenderPlayerView(player_t *player)
 		Portal_AddSkyboxPortals();
 
 	// Portal rendering. Hijacks the BSP traversal.
+	rs_sw_portaltime = I_GetTimeMicros();
 	if (portal_base)
 	{
 		portal_t *portal;
@@ -1502,15 +1526,20 @@ void R_RenderPlayerView(player_t *player)
 			Portal_Remove(portal);
 		}
 	}
+	rs_sw_portaltime = I_GetTimeMicros() - rs_sw_portaltime;
 
+	rs_sw_planetime = I_GetTimeMicros();
 	R_DrawPlanes();
 #ifdef FLOORSPLATS
 	R_DrawVisibleFloorSplats();
 #endif
+	rs_sw_planetime = I_GetTimeMicros() - rs_sw_planetime;
 
 	// draw mid texture and sprite
 	// And now 3D floors/sides!
+	rs_sw_maskedtime = I_GetTimeMicros();
 	R_DrawMasked(masks, nummasks);
+	rs_sw_maskedtime = I_GetTimeMicros() - rs_sw_maskedtime;
 
 	free(masks);
 }
diff --git a/src/r_main.h b/src/r_main.h
index 578eb3d54b7f79c2f49f16f40d71b7f5bda16d73..99a25d86ea4a20f9a4e23f9a8a7ce622b9a1c28e 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -74,6 +74,25 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y);
 
 boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph);
 
+// Render stats
+
+extern consvar_t cv_renderstats;
+
+extern int rs_prevframetime;// time when previous frame was rendered
+extern int rs_rendercalltime;
+extern int rs_swaptime;
+
+extern int rs_bsptime;
+
+extern int rs_sw_portaltime;
+extern int rs_sw_planetime;
+extern int rs_sw_maskedtime;
+
+extern int rs_numbspcalls;
+extern int rs_numsprites;
+extern int rs_numdrawnodes;
+extern int rs_numpolyobjects;
+
 //
 // REFRESH - the actual rendering functions.
 //
diff --git a/src/r_things.c b/src/r_things.c
index d2f3b49021d262b5576ff0319d34f0fd4794b24d..02c347929bd560cf6e7fc0589380c37a2bfdb1b9 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2552,6 +2552,8 @@ static drawnode_t *R_CreateDrawNode(drawnode_t *link)
 	node->thickseg = NULL;
 	node->ffloor = NULL;
 	node->sprite = NULL;
+
+	rs_numdrawnodes++;
 	return node;
 }
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index a86af316e9e22460c734057960f39a8301fc67e9..426904c63abf0525e5c22d7f1bc723293c1bd670 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2060,9 +2060,11 @@ static p_timeGetTime pfntimeGetTime = NULL;
 // but lower precision on Windows NT
 // ---------
 
-tic_t I_GetTime(void)
+DWORD TimeFunction(int requested_frequency)
 {
-	tic_t newtics = 0;
+	DWORD newtics = 0;
+	// this var acts as a multiplier if sub-millisecond precision is asked but is not available
+	int excess_frequency = requested_frequency / 1000;
 
 	if (!starttickcount) // high precision timer
 	{
@@ -2082,7 +2084,7 @@ tic_t I_GetTime(void)
 
 		if (frequency.LowPart && QueryPerformanceCounter(&currtime))
 		{
-			newtics = (INT32)((currtime.QuadPart - basetime.QuadPart) * NEWTICRATE
+			newtics = (INT32)((currtime.QuadPart - basetime.QuadPart) * requested_frequency
 				/ frequency.QuadPart);
 		}
 		else if (pfntimeGetTime)
@@ -2090,11 +2092,19 @@ tic_t I_GetTime(void)
 			currtime.LowPart = pfntimeGetTime();
 			if (!basetime.LowPart)
 				basetime.LowPart = currtime.LowPart;
-			newtics = ((currtime.LowPart - basetime.LowPart)/(1000/NEWTICRATE));
+			if (requested_frequency > 1000)
+				newtics = currtime.LowPart - basetime.LowPart * excess_frequency;
+			else
+				newtics = (currtime.LowPart - basetime.LowPart)/(1000/requested_frequency);
 		}
 	}
 	else
-		newtics = (GetTickCount() - starttickcount)/(1000/NEWTICRATE);
+	{
+		if (requested_frequency > 1000)
+			newtics = (GetTickCount() - starttickcount) * excess_frequency;
+		else
+			newtics = (GetTickCount() - starttickcount)/(1000/requested_frequency);
+	}
 
 	return newtics;
 }
@@ -2116,7 +2126,9 @@ static void I_ShutdownTimer(void)
 // I_GetTime
 // returns time in 1/TICRATE second tics
 //
-tic_t I_GetTime (void)
+
+// millisecond precision only
+int TimeFunction(int requested_frequency)
 {
 	static Uint64 basetime = 0;
 		   Uint64 ticks = SDL_GetTicks();
@@ -2126,14 +2138,24 @@ tic_t I_GetTime (void)
 
 	ticks -= basetime;
 
-	ticks = (ticks*TICRATE);
+	ticks = (ticks*requested_frequency);
 
 	ticks = (ticks/1000);
 
-	return (tic_t)ticks;
+	return ticks;
 }
 #endif
 
+tic_t I_GetTime(void)
+{
+	return TimeFunction(NEWTICRATE);
+}
+
+int I_GetTimeMicros(void)
+{
+	return TimeFunction(1000000);
+}
+
 //
 //I_StartupTimer
 //