1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2020 by Sonic Team Junior.
4 //
5 // This program is free software distributed under the
6 // terms of the GNU General Public License, version 2.
7 // See the 'LICENSE' file for more details.
8 //-----------------------------------------------------------------------------
9 /// \file m_perfstats.c
10 /// \brief Performance measurement tools.
11 
12 #include "m_perfstats.h"
13 #include "v_video.h"
14 #include "i_video.h"
15 #include "d_netcmd.h"
16 #include "r_main.h"
17 #include "i_system.h"
18 #include "z_zone.h"
19 #include "p_local.h"
20 
21 #ifdef HWRENDER
22 #include "hardware/hw_main.h"
23 #endif
24 
25 struct perfstatcol;
26 struct perfstatrow;
27 
28 typedef struct perfstatcol perfstatcol_t;
29 typedef struct perfstatrow perfstatrow_t;
30 
31 struct perfstatcol {
32 	INT32 lores_x;
33 	INT32 hires_x;
34 	INT32 color;
35 	perfstatrow_t * rows;
36 };
37 
38 struct perfstatrow {
39 	const char * lores_label;
40 	const char * hires_label;
41 	void       * value;
42 };
43 
44 static precise_t ps_frametime = 0;
45 
46 precise_t ps_tictime = 0;
47 
48 precise_t ps_playerthink_time = 0;
49 precise_t ps_thinkertime = 0;
50 
51 precise_t ps_thlist_times[NUM_THINKERLISTS];
52 
53 int ps_checkposition_calls = 0;
54 
55 precise_t ps_lua_thinkframe_time = 0;
56 int ps_lua_mobjhooks = 0;
57 
58 // dynamically allocated resizeable array for thinkframe hook stats
59 ps_hookinfo_t *thinkframe_hooks = NULL;
60 int thinkframe_hooks_length = 0;
61 int thinkframe_hooks_capacity = 16;
62 
63 static INT32 draw_row;
64 
PS_SetThinkFrameHookInfo(int index,precise_t time_taken,char * short_src)65 void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
66 {
67 	if (!thinkframe_hooks)
68 	{
69 		// array needs to be initialized
70 		thinkframe_hooks = Z_Malloc(sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity, PU_STATIC, NULL);
71 	}
72 	if (index >= thinkframe_hooks_capacity)
73 	{
74 		// array needs more space, realloc with double size
75 		thinkframe_hooks_capacity *= 2;
76 		thinkframe_hooks = Z_Realloc(thinkframe_hooks,
77 			sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity, PU_STATIC, NULL);
78 	}
79 	thinkframe_hooks[index].time_taken = time_taken;
80 	memcpy(thinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
81 	// since the values are set sequentially from begin to end, the last call should leave
82 	// the correct value to this variable
83 	thinkframe_hooks_length = index + 1;
84 }
85 
PS_SetFrameTime(void)86 static void PS_SetFrameTime(void)
87 {
88 	precise_t currenttime = I_GetPreciseTime();
89 	ps_frametime = currenttime - ps_prevframetime;
90 	ps_prevframetime = currenttime;
91 }
92 
M_HighResolution(void)93 static boolean M_HighResolution(void)
94 {
95 	return (vid.width >= 640 && vid.height >= 400);
96 }
97 
98 enum {
99 	PERF_TIME,
100 	PERF_COUNT,
101 };
102 
M_DrawPerfString(perfstatcol_t * col,int type)103 static void M_DrawPerfString(perfstatcol_t *col, int type)
104 {
105 	const boolean hires = M_HighResolution();
106 
107 	INT32 draw_flags = V_MONOSPACE | col->color;
108 
109 	perfstatrow_t * row;
110 
111 	int value;
112 
113 	if (hires)
114 		draw_flags |= V_ALLOWLOWERCASE;
115 
116 	for (row = col->rows; row->lores_label; ++row)
117 	{
118 		if (type == PERF_TIME)
119 			value = I_PreciseToMicros(*(precise_t *)row->value);
120 		else
121 			value = *(int *)row->value;
122 
123 		if (hires)
124 		{
125 			V_DrawSmallString(col->hires_x, draw_row, draw_flags,
126 					va("%s %d", row->hires_label, value));
127 
128 			draw_row += 5;
129 		}
130 		else
131 		{
132 			V_DrawThinString(col->lores_x, draw_row, draw_flags,
133 					va("%s %d", row->lores_label, value));
134 
135 			draw_row += 8;
136 		}
137 	}
138 }
139 
M_DrawPerfTiming(perfstatcol_t * col)140 static void M_DrawPerfTiming(perfstatcol_t *col)
141 {
142 	M_DrawPerfString(col, PERF_TIME);
143 }
144 
M_DrawPerfCount(perfstatcol_t * col)145 static void M_DrawPerfCount(perfstatcol_t *col)
146 {
147 	M_DrawPerfString(col, PERF_COUNT);
148 }
149 
M_DrawRenderStats(void)150 static void M_DrawRenderStats(void)
151 {
152 	const boolean hires = M_HighResolution();
153 
154 	const int half_row = hires ? 5 : 4;
155 
156 	precise_t extrarendertime;
157 
158 	perfstatrow_t frametime_row[] = {
159 		{"frmtime", "Frame time:    ", &ps_frametime},
160 		{0}
161 	};
162 
163 	perfstatrow_t rendercalltime_row[] = {
164 		{"drwtime", "3d rendering:  ", &ps_rendercalltime},
165 		{0}
166 	};
167 
168 	perfstatrow_t opengltime_row[] = {
169 		{"skybox ", "Skybox render: ", &ps_hw_skyboxtime},
170 		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
171 		{"nodesrt", "Drwnode sort:  ", &ps_hw_nodesorttime},
172 		{"nodedrw", "Drwnode render:", &ps_hw_nodedrawtime},
173 		{"sprsort", "Sprite sort:   ", &ps_hw_spritesorttime},
174 		{"sprdraw", "Sprite render: ", &ps_hw_spritedrawtime},
175 		{"other  ", "Other:         ", &extrarendertime},
176 		{0}
177 	};
178 
179 	perfstatrow_t softwaretime_row[] = {
180 		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
181 		{"sprclip", "R_ClipSprites: ", &ps_sw_spritecliptime},
182 		{"portals", "Portals+Skybox:", &ps_sw_portaltime},
183 		{"planes ", "R_DrawPlanes:  ", &ps_sw_planetime},
184 		{"masked ", "R_DrawMasked:  ", &ps_sw_maskedtime},
185 		{"other  ", "Other:         ", &extrarendertime},
186 		{0}
187 	};
188 
189 	perfstatrow_t uiswaptime_row[] = {
190 		{"ui     ", "UI render:     ", &ps_uitime},
191 		{"finupdt", "I_FinishUpdate:", &ps_swaptime},
192 		{0}
193 	};
194 
195 	perfstatrow_t tictime_row[] = {
196 		{"logic  ", "Game logic:    ", &ps_tictime},
197 		{0}
198 	};
199 
200 	perfstatrow_t rendercalls_row[] = {
201 		{"bspcall", "BSP calls:   ", &ps_numbspcalls},
202 		{"sprites", "Sprites:     ", &ps_numsprites},
203 		{"drwnode", "Drawnodes:   ", &ps_numdrawnodes},
204 		{"plyobjs", "Polyobjects: ", &ps_numpolyobjects},
205 		{0}
206 	};
207 
208 	perfstatrow_t batchtime_row[] = {
209 		{"batsort", "Batch sort:  ", &ps_hw_batchsorttime},
210 		{"batdraw", "Batch render:", &ps_hw_batchdrawtime},
211 		{0}
212 	};
213 
214 	perfstatrow_t batchcount_row[] = {
215 		{"polygon", "Polygons:  ", &ps_hw_numpolys},
216 		{"vertex ", "Vertices:  ", &ps_hw_numverts},
217 		{0}
218 	};
219 
220 	perfstatrow_t batchcalls_row[] = {
221 		{"drwcall", "Draw calls:", &ps_hw_numcalls},
222 		{"shaders", "Shaders:   ", &ps_hw_numshaders},
223 		{"texture", "Textures:  ", &ps_hw_numtextures},
224 		{"polyflg", "Polyflags: ", &ps_hw_numpolyflags},
225 		{"colors ", "Colors:    ", &ps_hw_numcolors},
226 		{0}
227 	};
228 
229 	perfstatcol_t      frametime_col =  {20,  20, V_YELLOWMAP,      frametime_row};
230 	perfstatcol_t rendercalltime_col =  {20,  20, V_YELLOWMAP, rendercalltime_row};
231 
232 	perfstatcol_t     opengltime_col =  {24,  24, V_YELLOWMAP,     opengltime_row};
233 	perfstatcol_t   softwaretime_col =  {24,  24, V_YELLOWMAP,   softwaretime_row};
234 
235 	perfstatcol_t     uiswaptime_col =  {20,  20, V_YELLOWMAP,     uiswaptime_row};
236 	perfstatcol_t        tictime_col =  {20,  20, V_GRAYMAP,          tictime_row};
237 
238 	perfstatcol_t    rendercalls_col =  {90, 115, V_BLUEMAP,      rendercalls_row};
239 
240 	perfstatcol_t      batchtime_col =  {90, 115, V_REDMAP,         batchtime_row};
241 
242 	perfstatcol_t     batchcount_col = {155, 200, V_PURPLEMAP,     batchcount_row};
243 	perfstatcol_t     batchcalls_col = {220, 200, V_PURPLEMAP,     batchcalls_row};
244 
245 
246 	boolean rendering = (
247 			gamestate == GS_LEVEL ||
248 			(gamestate == GS_TITLESCREEN && titlemapinaction)
249 	);
250 
251 	draw_row = 10;
252 	M_DrawPerfTiming(&frametime_col);
253 
254 	if (rendering)
255 	{
256 		M_DrawPerfTiming(&rendercalltime_col);
257 
258 		// Remember to update this calculation when adding more 3d rendering stats!
259 		extrarendertime = ps_rendercalltime - ps_bsptime;
260 
261 #ifdef HWRENDER
262 		if (rendermode == render_opengl)
263 		{
264 			extrarendertime -=
265 				ps_hw_skyboxtime +
266 				ps_hw_nodesorttime +
267 				ps_hw_nodedrawtime +
268 				ps_hw_spritesorttime +
269 				ps_hw_spritedrawtime;
270 
271 			if (cv_glbatching.value)
272 			{
273 				extrarendertime -=
274 					ps_hw_batchsorttime +
275 					ps_hw_batchdrawtime;
276 			}
277 
278 			M_DrawPerfTiming(&opengltime_col);
279 		}
280 		else
281 #endif
282 		{
283 			extrarendertime -=
284 				ps_sw_spritecliptime +
285 				ps_sw_portaltime +
286 				ps_sw_planetime +
287 				ps_sw_maskedtime;
288 
289 			M_DrawPerfTiming(&softwaretime_col);
290 		}
291 	}
292 
293 	M_DrawPerfTiming(&uiswaptime_col);
294 
295 	draw_row += half_row;
296 	M_DrawPerfTiming(&tictime_col);
297 
298 	if (rendering)
299 	{
300 		draw_row = 10;
301 		M_DrawPerfCount(&rendercalls_col);
302 
303 #ifdef HWRENDER
304 		if (rendermode == render_opengl && cv_glbatching.value)
305 		{
306 			draw_row += half_row;
307 			M_DrawPerfTiming(&batchtime_col);
308 
309 			draw_row = 10;
310 			M_DrawPerfCount(&batchcount_col);
311 
312 			if (hires)
313 				draw_row += half_row;
314 			else
315 				draw_row  = 10;
316 
317 			M_DrawPerfCount(&batchcalls_col);
318 		}
319 #endif
320 	}
321 }
322 
M_DrawTickStats(void)323 static void M_DrawTickStats(void)
324 {
325 	int i = 0;
326 	thinker_t *thinker;
327 	int thinkercount = 0;
328 	int polythcount = 0;
329 	int mainthcount = 0;
330 	int mobjcount = 0;
331 	int nothinkcount = 0;
332 	int scenerycount = 0;
333 	int regularcount = 0;
334 	int dynslopethcount = 0;
335 	int precipcount = 0;
336 	int removecount = 0;
337 
338 	precise_t extratime =
339 		ps_tictime -
340 		ps_playerthink_time -
341 		ps_thinkertime -
342 		ps_lua_thinkframe_time;
343 
344 	perfstatrow_t tictime_row[] = {
345 		{"logic  ", "Game logic:     ", &ps_tictime},
346 		{0}
347 	};
348 
349 	perfstatrow_t thinker_time_row[] = {
350 		{"plrthnk", "P_PlayerThink:  ", &ps_playerthink_time},
351 		{"thnkers", "P_RunThinkers:  ", &ps_thinkertime},
352 		{0}
353 	};
354 
355 	perfstatrow_t detailed_thinker_time_row[] = {
356 		{"plyobjs", "Polyobjects:    ", &ps_thlist_times[THINK_POLYOBJ]},
357 		{"main   ", "Main:           ", &ps_thlist_times[THINK_MAIN]},
358 		{"mobjs  ", "Mobjs:          ", &ps_thlist_times[THINK_MOBJ]},
359 		{"dynslop", "Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE]},
360 		{"precip ", "Precipitation:  ", &ps_thlist_times[THINK_PRECIP]},
361 		{0}
362 	};
363 
364 	perfstatrow_t extra_thinker_time_row[] = {
365 		{"lthinkf", "LUAh_ThinkFrame:", &ps_lua_thinkframe_time},
366 		{"other  ", "Other:          ", &extratime},
367 		{0}
368 	};
369 
370 	perfstatrow_t thinkercount_row[] = {
371 		{"thnkers", "Thinkers:       ", &thinkercount},
372 		{0}
373 	};
374 
375 	perfstatrow_t detailed_thinkercount_row[] = {
376 		{"plyobjs", "Polyobjects:    ", &polythcount},
377 		{"main   ", "Main:           ", &mainthcount},
378 		{"mobjs  ", "Mobjs:          ", &mobjcount},
379 		{0}
380 	};
381 
382 	perfstatrow_t mobjthinkercount_row[] = {
383 		{"regular", "Regular:        ", &regularcount},
384 		{"scenery", "Scenery:        ", &scenerycount},
385 		{0}
386 	};
387 
388 	perfstatrow_t nothinkcount_row[] = {
389 		{"nothink", "Nothink:        ", &nothinkcount},
390 		{0}
391 	};
392 
393 	perfstatrow_t detailed_thinkercount_row2[] = {
394 		{"dynslop", "Dynamic slopes: ", &dynslopethcount},
395 		{"precip ", "Precipitation:  ", &precipcount},
396 		{"remove ", "Pending removal:", &removecount},
397 		{0}
398 	};
399 
400 	perfstatrow_t misc_calls_row[] = {
401 		{"lmhook", "Lua mobj hooks: ", &ps_lua_mobjhooks},
402 		{"chkpos", "P_CheckPosition:", &ps_checkposition_calls},
403 		{0}
404 	};
405 
406 	perfstatcol_t               tictime_col  =  {20,  20, V_YELLOWMAP,               tictime_row};
407 	perfstatcol_t          thinker_time_col  =  {24,  24, V_YELLOWMAP,          thinker_time_row};
408 	perfstatcol_t detailed_thinker_time_col  =  {28,  28, V_YELLOWMAP, detailed_thinker_time_row};
409 	perfstatcol_t    extra_thinker_time_col  =  {24,  24, V_YELLOWMAP,    extra_thinker_time_row};
410 
411 	perfstatcol_t          thinkercount_col  =  {90, 115, V_BLUEMAP,            thinkercount_row};
412 	perfstatcol_t detailed_thinkercount_col  =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row};
413 	perfstatcol_t      mobjthinkercount_col  =  {98, 123, V_BLUEMAP,        mobjthinkercount_row};
414 	perfstatcol_t          nothinkcount_col  =  {98, 123, V_BLUEMAP,            nothinkcount_row};
415 	perfstatcol_t detailed_thinkercount_col2 =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row2};
416 	perfstatcol_t            misc_calls_col  = {170, 216, V_PURPLEMAP,            misc_calls_row};
417 
418 	for (i = 0; i < NUM_THINKERLISTS; i++)
419 	{
420 		for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
421 		{
422 			thinkercount++;
423 			if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
424 				removecount++;
425 			else if (i == THINK_POLYOBJ)
426 				polythcount++;
427 			else if (i == THINK_MAIN)
428 				mainthcount++;
429 			else if (i == THINK_MOBJ)
430 			{
431 				if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
432 				{
433 					mobj_t *mobj = (mobj_t*)thinker;
434 					mobjcount++;
435 					if (mobj->flags & MF_NOTHINK)
436 						nothinkcount++;
437 					else if (mobj->flags & MF_SCENERY)
438 						scenerycount++;
439 					else
440 						regularcount++;
441 				}
442 			}
443 			else if (i == THINK_DYNSLOPE)
444 				dynslopethcount++;
445 			else if (i == THINK_PRECIP)
446 				precipcount++;
447 		}
448 	}
449 
450 	draw_row = 10;
451 	M_DrawPerfTiming(&tictime_col);
452 	M_DrawPerfTiming(&thinker_time_col);
453 	M_DrawPerfTiming(&detailed_thinker_time_col);
454 	M_DrawPerfTiming(&extra_thinker_time_col);
455 
456 	draw_row = 10;
457 	M_DrawPerfCount(&thinkercount_col);
458 	M_DrawPerfCount(&detailed_thinkercount_col);
459 	M_DrawPerfCount(&mobjthinkercount_col);
460 
461 	if (nothinkcount)
462 		M_DrawPerfCount(&nothinkcount_col);
463 
464 	M_DrawPerfCount(&detailed_thinkercount_col2);
465 
466 	if (M_HighResolution())
467 	{
468 		V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, "Calls:");
469 
470 		draw_row = 15;
471 	}
472 	else
473 	{
474 		draw_row = 10;
475 	}
476 
477 	M_DrawPerfCount(&misc_calls_col);
478 }
479 
M_DrawPerfStats(void)480 void M_DrawPerfStats(void)
481 {
482 	char s[100];
483 
484 	PS_SetFrameTime();
485 
486 	if (cv_perfstats.value == 1) // rendering
487 	{
488 		M_DrawRenderStats();
489 	}
490 	else if (cv_perfstats.value == 2) // logic
491 	{
492 		M_DrawTickStats();
493 	}
494 	else if (cv_perfstats.value == 3) // lua thinkframe
495 	{
496 		if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
497 			return;
498 		if (vid.width < 640 || vid.height < 400) // low resolution
499 		{
500 			// it's not gonna fit very well..
501 			V_DrawThinString(30, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Not available for resolutions below 640x400");
502 		}
503 		else // high resolution
504 		{
505 			int i;
506 			// text writing position
507 			int x = 2;
508 			int y = 4;
509 			UINT32 text_color;
510 			char tempbuffer[LUA_IDSIZE];
511 			char last_mod_name[LUA_IDSIZE];
512 			last_mod_name[0] = '\0';
513 			for (i = 0; i < thinkframe_hooks_length; i++)
514 			{
515 				char* str = thinkframe_hooks[i].short_src;
516 				char* tempstr = tempbuffer;
517 				int len = (int)strlen(str);
518 				char* str_ptr;
519 				if (strcmp(".lua", str + len - 4) == 0)
520 				{
521 					str[len-4] = '\0'; // remove .lua at end
522 					len -= 4;
523 				}
524 				// we locate the wad/pk3 name in the string and compare it to
525 				// what we found on the previous iteration.
526 				// if the name has changed, print it out on the screen
527 				strcpy(tempstr, str);
528 				str_ptr = strrchr(tempstr, '|');
529 				if (str_ptr)
530 				{
531 					*str_ptr = '\0';
532 					str = str_ptr + 1; // this is the name of the hook without the mod file name
533 					str_ptr = strrchr(tempstr, PATHSEP[0]);
534 					if (str_ptr)
535 						tempstr = str_ptr + 1;
536 					// tempstr should now point to the mod name, (wad/pk3) possibly truncated
537 					if (strcmp(tempstr, last_mod_name) != 0)
538 					{
539 						strcpy(last_mod_name, tempstr);
540 						len = (int)strlen(tempstr);
541 						if (len > 25)
542 							tempstr += len - 25;
543 						snprintf(s, sizeof s - 1, "%s", tempstr);
544 						V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
545 						y += 4; // repeated code!
546 						if (y > 192)
547 						{
548 							y = 4;
549 							x += 106;
550 							if (x > 214)
551 								break;
552 						}
553 					}
554 					text_color = V_YELLOWMAP;
555 				}
556 				else
557 				{
558 					// probably a standalone lua file
559 					// cut off the folder if it's there
560 					str_ptr = strrchr(tempstr, PATHSEP[0]);
561 					if (str_ptr)
562 						str = str_ptr + 1;
563 					text_color = 0; // white
564 				}
565 				len = (int)strlen(str);
566 				if (len > 20)
567 					str += len - 20;
568 				snprintf(s, sizeof s - 1, "%20s: %d", str, I_PreciseToMicros(thinkframe_hooks[i].time_taken));
569 				V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s);
570 				y += 4; // repeated code!
571 				if (y > 192)
572 				{
573 					y = 4;
574 					x += 106;
575 					if (x > 214)
576 						break;
577 				}
578 			}
579 		}
580 	}
581 }
582