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: ", ®ularcount},
384 {"scenery", "Scenery: ", &scenerycount},
385 {0}
386 };
387
388 perfstatrow_t nothinkcount_row[] = {
389 {"nothink", "Nothink: ", ¬hinkcount},
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(¬hinkcount_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