mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-11-22 04:21:23 +00:00
945 lines
27 KiB
C
945 lines
27 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2020-2023 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file m_perfstats.c
|
|
/// \brief Performance measurement tools.
|
|
|
|
#include "m_perfstats.h"
|
|
#include "v_video.h"
|
|
#include "i_video.h"
|
|
#include "netcode/d_netcmd.h"
|
|
#include "r_main.h"
|
|
#include "i_system.h"
|
|
#include "z_zone.h"
|
|
#include "p_local.h"
|
|
#include "r_fps.h"
|
|
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_main.h"
|
|
#endif
|
|
|
|
struct perfstatrow;
|
|
|
|
typedef struct perfstatrow perfstatrow_t;
|
|
|
|
struct perfstatrow {
|
|
const char * lores_label;
|
|
const char * hires_label;
|
|
ps_metric_t * metric;
|
|
UINT8 flags;
|
|
};
|
|
|
|
// perfstatrow_t flags
|
|
|
|
#define PS_TIME 1 // metric measures time (uses precise_t instead of INT32)
|
|
#define PS_LEVEL 2 // metric is valid only when a level is active
|
|
#define PS_SW 4 // metric is valid only in software mode
|
|
#define PS_HW 8 // metric is valid only in opengl mode
|
|
#define PS_BATCHING 16 // metric is valid only when opengl batching is active
|
|
#define PS_HIDE_ZERO 32 // hide metric if its value is zero
|
|
|
|
static ps_metric_t ps_frametime = {0};
|
|
|
|
ps_metric_t ps_tictime = {0};
|
|
|
|
ps_metric_t ps_playerthink_time = {0};
|
|
ps_metric_t ps_thinkertime = {0};
|
|
|
|
ps_metric_t ps_thlist_times[NUM_THINKERLISTS];
|
|
|
|
static ps_metric_t ps_thinkercount = {0};
|
|
static ps_metric_t ps_polythcount = {0};
|
|
static ps_metric_t ps_mainthcount = {0};
|
|
static ps_metric_t ps_mobjcount = {0};
|
|
static ps_metric_t ps_regularcount = {0};
|
|
static ps_metric_t ps_scenerycount = {0};
|
|
static ps_metric_t ps_nothinkcount = {0};
|
|
static ps_metric_t ps_dynslopethcount = {0};
|
|
static ps_metric_t ps_precipcount = {0};
|
|
static ps_metric_t ps_removecount = {0};
|
|
|
|
ps_metric_t ps_checkposition_calls = {0};
|
|
|
|
ps_metric_t ps_lua_prethinkframe_time = {0};
|
|
ps_metric_t ps_lua_thinkframe_time = {0};
|
|
ps_metric_t ps_lua_postthinkframe_time = {0};
|
|
|
|
ps_metric_t ps_lua_mobjhooks = {0};
|
|
|
|
ps_metric_t ps_otherlogictime = {0};
|
|
|
|
// Columns for perfstats pages.
|
|
|
|
// Position on screen is determined separately in the drawing functions.
|
|
|
|
// New columns must also be added to the drawing and update functions.
|
|
// Drawing functions: PS_DrawRenderStats, PS_DrawGameLogicStats, etc.
|
|
// Update functions:
|
|
// - PS_UpdateFrameStats for frame-dependent values
|
|
// - PS_UpdateTickStats for tick-dependent values
|
|
|
|
// Rendering stats columns
|
|
|
|
perfstatrow_t rendertime_rows[] = {
|
|
{"frmtime", "Frame time: ", &ps_frametime, PS_TIME},
|
|
{"drwtime", "3d rendering: ", &ps_rendercalltime, PS_TIME|PS_LEVEL},
|
|
|
|
#ifdef HWRENDER
|
|
{" skybox ", " Skybox render: ", &ps_hw_skyboxtime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" bsptime", " RenderBSPNode: ", &ps_bsptime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" batsort", " Batch sort: ", &ps_hw_batchsorttime, PS_TIME|PS_LEVEL|PS_HW|PS_BATCHING},
|
|
{" batdraw", " Batch render: ", &ps_hw_batchdrawtime, PS_TIME|PS_LEVEL|PS_HW|PS_BATCHING},
|
|
{" sprsort", " Sprite sort: ", &ps_hw_spritesorttime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" sprdraw", " Sprite render: ", &ps_hw_spritedrawtime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" nodesrt", " Drwnode sort: ", &ps_hw_nodesorttime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" nodedrw", " Drwnode render:", &ps_hw_nodedrawtime, PS_TIME|PS_LEVEL|PS_HW},
|
|
{" other ", " Other: ", &ps_otherrendertime, PS_TIME|PS_LEVEL|PS_HW},
|
|
#endif
|
|
|
|
{" bsptime", " RenderBSPNode: ", &ps_bsptime, PS_TIME|PS_LEVEL|PS_SW},
|
|
{" sprclip", " R_ClipSprites: ", &ps_sw_spritecliptime, PS_TIME|PS_LEVEL|PS_SW},
|
|
{" portals", " Portals+Skybox:", &ps_sw_portaltime, PS_TIME|PS_LEVEL|PS_SW},
|
|
{" planes ", " R_DrawPlanes: ", &ps_sw_planetime, PS_TIME|PS_LEVEL|PS_SW},
|
|
{" masked ", " R_DrawMasked: ", &ps_sw_maskedtime, PS_TIME|PS_LEVEL|PS_SW},
|
|
{" other ", " Other: ", &ps_otherrendertime, PS_TIME|PS_LEVEL|PS_SW},
|
|
|
|
{"ui ", "UI render: ", &ps_uitime, PS_TIME},
|
|
{"finupdt", "I_FinishUpdate:", &ps_swaptime, PS_TIME},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t gamelogicbrief_row[] = {
|
|
{"logic ", "Game logic: ", &ps_tictime, PS_TIME},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t commoncounter_rows[] = {
|
|
{"bspcall", "BSP calls: ", &ps_numbspcalls, 0},
|
|
{"sprites", "Sprites: ", &ps_numsprites, 0},
|
|
{"drwnode", "Drawnodes: ", &ps_numdrawnodes, 0},
|
|
{"plyobjs", "Polyobjects: ", &ps_numpolyobjects, 0},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t interpolation_rows[] = {
|
|
{"intpfrc", "Interp frac: ", &ps_interp_frac, PS_TIME},
|
|
{"intplag", "Interp lag: ", &ps_interp_lag, PS_TIME},
|
|
{0}
|
|
};
|
|
|
|
#ifdef HWRENDER
|
|
perfstatrow_t batchcount_rows[] = {
|
|
{"polygon", "Polygons: ", &ps_hw_numpolys, 0},
|
|
{"vertex ", "Vertices: ", &ps_hw_numverts, 0},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t batchcalls_rows[] = {
|
|
{"drwcall", "Draw calls:", &ps_hw_numcalls, 0},
|
|
{"shaders", "Shaders: ", &ps_hw_numshaders, 0},
|
|
{"texture", "Textures: ", &ps_hw_numtextures, 0},
|
|
{"polyflg", "Polyflags: ", &ps_hw_numpolyflags, 0},
|
|
{"colors ", "Colors: ", &ps_hw_numcolors, 0},
|
|
{0}
|
|
};
|
|
#endif
|
|
|
|
// Game logic stats columns
|
|
|
|
perfstatrow_t gamelogic_rows[] = {
|
|
{"logic ", "Game logic: ", &ps_tictime, PS_TIME},
|
|
{" plrthnk", " P_PlayerThink: ", &ps_playerthink_time, PS_TIME|PS_LEVEL},
|
|
{" thnkers", " P_RunThinkers: ", &ps_thinkertime, PS_TIME|PS_LEVEL},
|
|
{" plyobjs", " Polyobjects: ", &ps_thlist_times[THINK_POLYOBJ], PS_TIME|PS_LEVEL},
|
|
{" main ", " Main: ", &ps_thlist_times[THINK_MAIN], PS_TIME|PS_LEVEL},
|
|
{" mobjs ", " Mobjs: ", &ps_thlist_times[THINK_MOBJ], PS_TIME|PS_LEVEL},
|
|
{" dynslop", " Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE], PS_TIME|PS_LEVEL},
|
|
{" precip ", " Precipitation: ", &ps_thlist_times[THINK_PRECIP], PS_TIME|PS_LEVEL},
|
|
{" lprethinkf", " LUAh_PreThinkFrame:", &ps_lua_prethinkframe_time, PS_TIME|PS_LEVEL},
|
|
{" lthinkf", " LUAh_ThinkFrame:", &ps_lua_thinkframe_time, PS_TIME|PS_LEVEL},
|
|
{" lpostthinkf", " LUAh_PostThinkFrame:", &ps_lua_postthinkframe_time, PS_TIME|PS_LEVEL},
|
|
{" other ", " Other: ", &ps_otherlogictime, PS_TIME|PS_LEVEL},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t thinkercount_rows[] = {
|
|
{"thnkers", "Thinkers: ", &ps_thinkercount, PS_LEVEL},
|
|
{" plyobjs", " Polyobjects: ", &ps_polythcount, PS_LEVEL},
|
|
{" main ", " Main: ", &ps_mainthcount, PS_LEVEL},
|
|
{" mobjs ", " Mobjs: ", &ps_mobjcount, PS_LEVEL},
|
|
{" regular", " Regular: ", &ps_regularcount, PS_LEVEL},
|
|
{" scenery", " Scenery: ", &ps_scenerycount, PS_LEVEL},
|
|
{" nothink", " Nothink: ", &ps_nothinkcount, PS_HIDE_ZERO|PS_LEVEL},
|
|
{" dynslop", " Dynamic slopes: ", &ps_dynslopethcount, PS_LEVEL},
|
|
{" precip ", " Precipitation: ", &ps_precipcount, PS_LEVEL},
|
|
{" remove ", " Pending removal:", &ps_removecount, PS_LEVEL},
|
|
{0}
|
|
};
|
|
|
|
perfstatrow_t misc_calls_rows[] = {
|
|
{"lmhook", "Lua mobj hooks: ", &ps_lua_mobjhooks, PS_LEVEL},
|
|
{"chkpos", "P_CheckPosition:", &ps_checkposition_calls, PS_LEVEL},
|
|
{0}
|
|
};
|
|
|
|
// Sample collection status for averaging.
|
|
// Maximum of these two is shown to user if nonzero to tell that
|
|
// the reported averages are not correct yet.
|
|
int ps_frame_samples_left = 0;
|
|
int ps_tick_samples_left = 0;
|
|
// History writing positions for frame and tick based metrics
|
|
int ps_frame_index = 0;
|
|
int ps_tick_index = 0;
|
|
|
|
// dynamically allocated resizeable array for thinkframe hook stats
|
|
ps_hookinfo_t *prethinkframe_hooks = NULL;
|
|
int prethinkframe_hooks_length = 0;
|
|
int prethinkframe_hooks_capacity = 16;
|
|
|
|
ps_hookinfo_t *thinkframe_hooks = NULL;
|
|
int thinkframe_hooks_length = 0;
|
|
int thinkframe_hooks_capacity = 16;
|
|
|
|
ps_hookinfo_t *postthinkframe_hooks = NULL;
|
|
int postthinkframe_hooks_length = 0;
|
|
int postthinkframe_hooks_capacity = 16;
|
|
|
|
void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
|
|
{
|
|
if (!prethinkframe_hooks)
|
|
{
|
|
// array needs to be initialized
|
|
prethinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity, PU_STATIC, NULL);
|
|
}
|
|
if (index >= prethinkframe_hooks_capacity)
|
|
{
|
|
// array needs more space, realloc with double size
|
|
int new_capacity = prethinkframe_hooks_capacity * 2;
|
|
prethinkframe_hooks = Z_Realloc(prethinkframe_hooks,
|
|
sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL);
|
|
// initialize new memory with zeros so the pointers in the structs are null
|
|
memset(&prethinkframe_hooks[prethinkframe_hooks_capacity], 0,
|
|
sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity);
|
|
prethinkframe_hooks_capacity = new_capacity;
|
|
}
|
|
prethinkframe_hooks[index].time_taken.value.p = time_taken;
|
|
memcpy(prethinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
|
|
// since the values are set sequentially from begin to end, the last call should leave
|
|
// the correct value to this variable
|
|
prethinkframe_hooks_length = index + 1;
|
|
}
|
|
|
|
void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
|
|
{
|
|
if (!thinkframe_hooks)
|
|
{
|
|
// array needs to be initialized
|
|
thinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity, PU_STATIC, NULL);
|
|
}
|
|
if (index >= thinkframe_hooks_capacity)
|
|
{
|
|
// array needs more space, realloc with double size
|
|
int new_capacity = thinkframe_hooks_capacity * 2;
|
|
thinkframe_hooks = Z_Realloc(thinkframe_hooks,
|
|
sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL);
|
|
// initialize new memory with zeros so the pointers in the structs are null
|
|
memset(&thinkframe_hooks[thinkframe_hooks_capacity], 0,
|
|
sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity);
|
|
thinkframe_hooks_capacity = new_capacity;
|
|
}
|
|
thinkframe_hooks[index].time_taken.value.p = time_taken;
|
|
memcpy(thinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
|
|
// since the values are set sequentially from begin to end, the last call should leave
|
|
// the correct value to this variable
|
|
thinkframe_hooks_length = index + 1;
|
|
}
|
|
|
|
void PS_SetPostThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
|
|
{
|
|
if (!postthinkframe_hooks)
|
|
{
|
|
// array needs to be initialized
|
|
postthinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity, PU_STATIC, NULL);
|
|
}
|
|
if (index >= postthinkframe_hooks_capacity)
|
|
{
|
|
// array needs more space, realloc with double size
|
|
int new_capacity = postthinkframe_hooks_capacity * 2;
|
|
postthinkframe_hooks = Z_Realloc(postthinkframe_hooks,
|
|
sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL);
|
|
// initialize new memory with zeros so the pointers in the structs are null
|
|
memset(&postthinkframe_hooks[postthinkframe_hooks_capacity], 0,
|
|
sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity);
|
|
postthinkframe_hooks_capacity = new_capacity;
|
|
}
|
|
postthinkframe_hooks[index].time_taken.value.p = time_taken;
|
|
memcpy(postthinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
|
|
// since the values are set sequentially from begin to end, the last call should leave
|
|
// the correct value to this variable
|
|
postthinkframe_hooks_length = index + 1;
|
|
}
|
|
|
|
static boolean PS_HighResolution(void)
|
|
{
|
|
return (vid.width >= 640 && vid.height >= 400);
|
|
}
|
|
|
|
static boolean PS_IsLevelActive(void)
|
|
{
|
|
return gamestate == GS_LEVEL ||
|
|
(gamestate == GS_TITLESCREEN && titlemapinaction);
|
|
}
|
|
|
|
// Is the row valid in the current context?
|
|
static boolean PS_IsRowValid(perfstatrow_t *row)
|
|
{
|
|
return !((row->flags & PS_LEVEL && !PS_IsLevelActive())
|
|
|| (row->flags & PS_SW && rendermode != render_soft)
|
|
|| (row->flags & PS_HW && rendermode != render_opengl)
|
|
#ifdef HWRENDER
|
|
|| (row->flags & PS_BATCHING && !cv_glbatching.value)
|
|
#endif
|
|
);
|
|
}
|
|
|
|
// Should the row be visible on the screen?
|
|
static boolean PS_IsRowVisible(perfstatrow_t *row)
|
|
{
|
|
boolean value_is_zero;
|
|
|
|
if (row->flags & PS_TIME)
|
|
value_is_zero = row->metric->value.p == 0;
|
|
else
|
|
value_is_zero = row->metric->value.i == 0;
|
|
|
|
return !(!PS_IsRowValid(row) ||
|
|
(row->flags & PS_HIDE_ZERO && value_is_zero));
|
|
}
|
|
|
|
static INT32 PS_GetMetricAverage(ps_metric_t *metric, boolean time_metric)
|
|
{
|
|
char* history_read_pos = metric->history; // char* used for pointer arithmetic
|
|
INT64 sum = 0;
|
|
int i;
|
|
int value_size = time_metric ? sizeof(precise_t) : sizeof(INT32);
|
|
|
|
for (i = 0; i < cv_ps_samplesize.value; i++)
|
|
{
|
|
if (time_metric)
|
|
sum += (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
|
|
else
|
|
sum += *((INT32*)history_read_pos);
|
|
history_read_pos += value_size;
|
|
}
|
|
|
|
return sum / cv_ps_samplesize.value;
|
|
}
|
|
|
|
static INT32 PS_GetMetricMinOrMax(ps_metric_t *metric, boolean time_metric, boolean get_max)
|
|
{
|
|
char* history_read_pos = metric->history; // char* used for pointer arithmetic
|
|
INT32 found_value = get_max ? INT32_MIN : INT32_MAX;
|
|
int i;
|
|
int value_size = time_metric ? sizeof(precise_t) : sizeof(INT32);
|
|
|
|
for (i = 0; i < cv_ps_samplesize.value; i++)
|
|
{
|
|
INT32 value;
|
|
if (time_metric)
|
|
value = (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
|
|
else
|
|
value = *((INT32*)history_read_pos);
|
|
|
|
if ((get_max && value > found_value) ||
|
|
(!get_max && value < found_value))
|
|
{
|
|
found_value = value;
|
|
}
|
|
history_read_pos += value_size;
|
|
}
|
|
|
|
return found_value;
|
|
}
|
|
|
|
// Calculates the standard deviation for metric.
|
|
static INT32 PS_GetMetricSD(ps_metric_t *metric, boolean time_metric)
|
|
{
|
|
char* history_read_pos = metric->history; // char* used for pointer arithmetic
|
|
INT64 sum = 0;
|
|
int i;
|
|
int value_size = time_metric ? sizeof(precise_t) : sizeof(INT32);
|
|
INT32 avg = PS_GetMetricAverage(metric, time_metric);
|
|
|
|
for (i = 0; i < cv_ps_samplesize.value; i++)
|
|
{
|
|
INT64 value;
|
|
if (time_metric)
|
|
value = (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
|
|
else
|
|
value = *((INT32*)history_read_pos);
|
|
|
|
value -= avg;
|
|
sum += value * value;
|
|
|
|
history_read_pos += value_size;
|
|
}
|
|
|
|
return round(sqrt(sum / cv_ps_samplesize.value));
|
|
}
|
|
|
|
// Returns the value to show on screen for metric.
|
|
static INT32 PS_GetMetricScreenValue(ps_metric_t *metric, boolean time_metric)
|
|
{
|
|
if (cv_ps_samplesize.value > 1 && metric->history)
|
|
{
|
|
if (cv_ps_descriptor.value == 1)
|
|
return PS_GetMetricAverage(metric, time_metric);
|
|
else if (cv_ps_descriptor.value == 2)
|
|
return PS_GetMetricSD(metric, time_metric);
|
|
else if (cv_ps_descriptor.value == 3)
|
|
return PS_GetMetricMinOrMax(metric, time_metric, false);
|
|
else
|
|
return PS_GetMetricMinOrMax(metric, time_metric, true);
|
|
}
|
|
else
|
|
{
|
|
if (time_metric)
|
|
return (metric->value.p) / (I_GetPrecisePrecision() / 1000000);
|
|
else
|
|
return metric->value.i;
|
|
}
|
|
}
|
|
|
|
static int PS_DrawPerfRows(int x, int y, int color, perfstatrow_t *rows)
|
|
{
|
|
const boolean hires = PS_HighResolution();
|
|
INT32 draw_flags = V_MONOSPACE | color;
|
|
perfstatrow_t * row;
|
|
int draw_y = y;
|
|
|
|
if (hires)
|
|
draw_flags |= V_ALLOWLOWERCASE;
|
|
|
|
for (row = rows; row->lores_label; ++row)
|
|
{
|
|
const char *label;
|
|
INT32 value;
|
|
char *final_str;
|
|
|
|
if (!PS_IsRowVisible(row))
|
|
continue;
|
|
|
|
label = hires ? row->hires_label : row->lores_label;
|
|
value = PS_GetMetricScreenValue(row->metric, !!(row->flags & PS_TIME));
|
|
final_str = va("%s %d", label, value);
|
|
|
|
if (hires)
|
|
{
|
|
V_DrawSmallString(x, draw_y, draw_flags, final_str);
|
|
draw_y += 5;
|
|
}
|
|
else
|
|
{
|
|
V_DrawThinString(x, draw_y, draw_flags, final_str);
|
|
draw_y += 8;
|
|
}
|
|
}
|
|
|
|
return draw_y;
|
|
}
|
|
|
|
static void PS_UpdateMetricHistory(ps_metric_t *metric, boolean time_metric, boolean frame_metric, boolean set_user)
|
|
{
|
|
int index = frame_metric ? ps_frame_index : ps_tick_index;
|
|
|
|
if (!metric->history)
|
|
{
|
|
// allocate history table
|
|
int value_size = time_metric ? sizeof(precise_t) : sizeof(INT32);
|
|
void** memory_user = set_user ? &metric->history : NULL;
|
|
|
|
metric->history = Z_Calloc(value_size * cv_ps_samplesize.value, PU_PERFSTATS,
|
|
memory_user);
|
|
|
|
// reset "samples left" counter since this history table needs to be filled
|
|
if (frame_metric)
|
|
ps_frame_samples_left = cv_ps_samplesize.value;
|
|
else
|
|
ps_tick_samples_left = cv_ps_samplesize.value;
|
|
}
|
|
|
|
if (time_metric)
|
|
{
|
|
precise_t *history = (precise_t*)metric->history;
|
|
history[index] = metric->value.p;
|
|
}
|
|
else
|
|
{
|
|
INT32 *history = (INT32*)metric->history;
|
|
history[index] = metric->value.i;
|
|
}
|
|
}
|
|
|
|
static void PS_UpdateRowHistories(perfstatrow_t *rows, boolean frame_metric)
|
|
{
|
|
perfstatrow_t *row;
|
|
for (row = rows; row->lores_label; row++)
|
|
{
|
|
if (PS_IsRowValid(row))
|
|
PS_UpdateMetricHistory(row->metric, !!(row->flags & PS_TIME), frame_metric, true);
|
|
}
|
|
}
|
|
|
|
// Update all metrics that are calculated on every frame.
|
|
static void PS_UpdateFrameStats(void)
|
|
{
|
|
// update frame time
|
|
precise_t currenttime = I_GetPreciseTime();
|
|
ps_frametime.value.p = currenttime - ps_prevframetime;
|
|
ps_prevframetime = currenttime;
|
|
|
|
// update 3d rendering stats
|
|
if (PS_IsLevelActive())
|
|
{
|
|
// Remember to update this calculation when adding more 3d rendering stats!
|
|
ps_otherrendertime.value.p = ps_rendercalltime.value.p - ps_bsptime.value.p;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl)
|
|
{
|
|
ps_otherrendertime.value.p -=
|
|
ps_hw_skyboxtime.value.p +
|
|
ps_hw_nodesorttime.value.p +
|
|
ps_hw_nodedrawtime.value.p +
|
|
ps_hw_spritesorttime.value.p +
|
|
ps_hw_spritedrawtime.value.p;
|
|
|
|
if (cv_glbatching.value)
|
|
{
|
|
ps_otherrendertime.value.p -=
|
|
ps_hw_batchsorttime.value.p +
|
|
ps_hw_batchdrawtime.value.p;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ps_otherrendertime.value.p -=
|
|
ps_sw_spritecliptime.value.p +
|
|
ps_sw_portaltime.value.p +
|
|
ps_sw_planetime.value.p +
|
|
ps_sw_maskedtime.value.p;
|
|
}
|
|
}
|
|
|
|
if (cv_ps_samplesize.value > 1)
|
|
{
|
|
PS_UpdateRowHistories(rendertime_rows, true);
|
|
if (PS_IsLevelActive())
|
|
PS_UpdateRowHistories(commoncounter_rows, true);
|
|
|
|
if (R_UsingFrameInterpolation())
|
|
PS_UpdateRowHistories(interpolation_rows, true);
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl && cv_glbatching.value)
|
|
{
|
|
PS_UpdateRowHistories(batchcount_rows, true);
|
|
PS_UpdateRowHistories(batchcalls_rows, true);
|
|
}
|
|
#endif
|
|
|
|
ps_frame_index++;
|
|
if (ps_frame_index >= cv_ps_samplesize.value)
|
|
ps_frame_index = 0;
|
|
if (ps_frame_samples_left)
|
|
ps_frame_samples_left--;
|
|
}
|
|
}
|
|
|
|
// Update thinker counters by iterating the thinker lists.
|
|
static void PS_CountThinkers(void)
|
|
{
|
|
int i;
|
|
thinker_t *thinker;
|
|
|
|
ps_thinkercount.value.i = 0;
|
|
ps_polythcount.value.i = 0;
|
|
ps_mainthcount.value.i = 0;
|
|
ps_mobjcount.value.i = 0;
|
|
ps_regularcount.value.i = 0;
|
|
ps_scenerycount.value.i = 0;
|
|
ps_nothinkcount.value.i = 0;
|
|
ps_dynslopethcount.value.i = 0;
|
|
ps_precipcount.value.i = 0;
|
|
ps_removecount.value.i = 0;
|
|
|
|
for (i = 0; i < NUM_THINKERLISTS; i++)
|
|
{
|
|
for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
|
|
{
|
|
ps_thinkercount.value.i++;
|
|
if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
|
|
ps_removecount.value.i++;
|
|
else if (i == THINK_POLYOBJ)
|
|
ps_polythcount.value.i++;
|
|
else if (i == THINK_MAIN)
|
|
ps_mainthcount.value.i++;
|
|
else if (i == THINK_MOBJ)
|
|
{
|
|
if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
|
|
{
|
|
mobj_t *mobj = (mobj_t*)thinker;
|
|
ps_mobjcount.value.i++;
|
|
if (mobj->flags & MF_NOTHINK)
|
|
ps_nothinkcount.value.i++;
|
|
else if (mobj->flags & MF_SCENERY)
|
|
ps_scenerycount.value.i++;
|
|
else
|
|
ps_regularcount.value.i++;
|
|
}
|
|
}
|
|
else if (i == THINK_DYNSLOPE)
|
|
ps_dynslopethcount.value.i++;
|
|
else if (i == THINK_PRECIP)
|
|
ps_precipcount.value.i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update all metrics that are calculated on every tick.
|
|
void PS_UpdateTickStats(void)
|
|
{
|
|
if (cv_perfstats.value == 1 && cv_ps_samplesize.value > 1)
|
|
{
|
|
PS_UpdateRowHistories(gamelogicbrief_row, false);
|
|
}
|
|
if (cv_perfstats.value == 2)
|
|
{
|
|
if (PS_IsLevelActive())
|
|
{
|
|
ps_otherlogictime.value.p =
|
|
ps_tictime.value.p -
|
|
ps_playerthink_time.value.p -
|
|
ps_thinkertime.value.p -
|
|
ps_lua_prethinkframe_time.value.p -
|
|
ps_lua_thinkframe_time.value.p -
|
|
ps_lua_postthinkframe_time.value.p;
|
|
|
|
PS_CountThinkers();
|
|
}
|
|
|
|
if (cv_ps_samplesize.value > 1)
|
|
{
|
|
PS_UpdateRowHistories(gamelogic_rows, false);
|
|
PS_UpdateRowHistories(thinkercount_rows, false);
|
|
PS_UpdateRowHistories(misc_calls_rows, false);
|
|
}
|
|
}
|
|
if (cv_ps_samplesize.value > 1)
|
|
{
|
|
if(cv_perfstats.value >= 3 && PS_IsLevelActive())
|
|
{
|
|
int i;
|
|
if (cv_perfstats.value == 3)
|
|
{
|
|
for (i = 0; i < thinkframe_hooks_length; i++)
|
|
PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false);
|
|
}
|
|
else if (cv_perfstats.value == 4)
|
|
{
|
|
for (i = 0; i < prethinkframe_hooks_length; i++)
|
|
PS_UpdateMetricHistory(&prethinkframe_hooks[i].time_taken, true, false, false);
|
|
}
|
|
else if (cv_perfstats.value == 5)
|
|
{
|
|
for (i = 0; i < postthinkframe_hooks_length; i++)
|
|
PS_UpdateMetricHistory(&postthinkframe_hooks[i].time_taken, true, false, false);
|
|
}
|
|
}
|
|
if (cv_perfstats.value)
|
|
{
|
|
ps_tick_index++;
|
|
if (ps_tick_index >= cv_ps_samplesize.value)
|
|
ps_tick_index = 0;
|
|
if (ps_tick_samples_left)
|
|
ps_tick_samples_left--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void PS_DrawDescriptorHeader(void)
|
|
{
|
|
if (cv_ps_samplesize.value > 1)
|
|
{
|
|
const char* descriptor_names[] = {
|
|
"average",
|
|
"standard deviation",
|
|
"minimum",
|
|
"maximum"
|
|
};
|
|
const boolean hires = PS_HighResolution();
|
|
char* str;
|
|
INT32 flags = V_MONOSPACE | V_ALLOWLOWERCASE;
|
|
int samples_left = max(ps_frame_samples_left, ps_tick_samples_left);
|
|
int x, y;
|
|
|
|
if (cv_perfstats.value >= 3)
|
|
{
|
|
x = 2;
|
|
y = 0;
|
|
}
|
|
else
|
|
{
|
|
x = 20;
|
|
y = hires ? 5 : 2;
|
|
}
|
|
|
|
if (samples_left)
|
|
{
|
|
str = va("Samples needed for correct results: %d", samples_left);
|
|
flags |= V_REDMAP;
|
|
}
|
|
else
|
|
{
|
|
str = va("Showing the %s of %d samples.",
|
|
descriptor_names[cv_ps_descriptor.value - 1], cv_ps_samplesize.value);
|
|
flags |= V_GREENMAP;
|
|
}
|
|
|
|
if (hires)
|
|
V_DrawSmallString(x, y, flags, str);
|
|
else
|
|
V_DrawThinString(x, y, flags, str);
|
|
}
|
|
}
|
|
|
|
static void PS_DrawRenderStats(void)
|
|
{
|
|
const boolean hires = PS_HighResolution();
|
|
const int half_row = hires ? 5 : 4;
|
|
int x, y, cy = 10;
|
|
|
|
PS_DrawDescriptorHeader();
|
|
|
|
y = PS_DrawPerfRows(20, 10, V_YELLOWMAP, rendertime_rows);
|
|
|
|
PS_DrawPerfRows(20, y + half_row, V_GRAYMAP, gamelogicbrief_row);
|
|
|
|
if (PS_IsLevelActive())
|
|
{
|
|
x = hires ? 115 : 90;
|
|
cy = PS_DrawPerfRows(x, 10, V_BLUEMAP, commoncounter_rows) + half_row;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl && cv_glbatching.value)
|
|
{
|
|
x = hires ? 200 : 155;
|
|
y = PS_DrawPerfRows(x, 10, V_PURPLEMAP, batchcount_rows);
|
|
|
|
x = hires ? 200 : 220;
|
|
y = hires ? y + half_row : 10;
|
|
PS_DrawPerfRows(x, y, V_PURPLEMAP, batchcalls_rows);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (R_UsingFrameInterpolation())
|
|
{
|
|
x = hires ? 115 : 90;
|
|
PS_DrawPerfRows(x, cy, V_ROSYMAP, interpolation_rows);
|
|
}
|
|
}
|
|
|
|
static void PS_DrawGameLogicStats(void)
|
|
{
|
|
const boolean hires = PS_HighResolution();
|
|
int x, y;
|
|
|
|
PS_DrawDescriptorHeader();
|
|
|
|
PS_DrawPerfRows(20, 10, V_YELLOWMAP, gamelogic_rows);
|
|
|
|
x = hires ? 115 : 90;
|
|
PS_DrawPerfRows(x, 10, V_BLUEMAP, thinkercount_rows);
|
|
|
|
if (hires)
|
|
V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, "Calls:");
|
|
|
|
x = hires ? 216 : 170;
|
|
y = hires ? 15 : 10;
|
|
PS_DrawPerfRows(x, y, V_PURPLEMAP, misc_calls_rows);
|
|
}
|
|
|
|
static void draw_think_frame_stats(int hook_length, ps_hookinfo_t *hook)
|
|
{
|
|
char s[100];
|
|
int i;
|
|
// text writing position
|
|
int x = 2;
|
|
int y = 4;
|
|
UINT32 text_color;
|
|
char tempbuffer[LUA_IDSIZE];
|
|
char last_mod_name[LUA_IDSIZE];
|
|
last_mod_name[0] = '\0';
|
|
|
|
PS_DrawDescriptorHeader();
|
|
|
|
for (i = 0; i < hook_length; i++)
|
|
{
|
|
|
|
#define NEXT_ROW() \
|
|
y += 4; \
|
|
if (y > 192) \
|
|
{ \
|
|
y = 4; \
|
|
x += 106; \
|
|
if (x > 214) \
|
|
break; \
|
|
}
|
|
|
|
char* str = hook[i].short_src;
|
|
char* tempstr = tempbuffer;
|
|
int len = (int)strlen(str);
|
|
char* str_ptr;
|
|
if (strcmp(".lua", str + len - 4) == 0)
|
|
{
|
|
str[len-4] = '\0'; // remove .lua at end
|
|
len -= 4;
|
|
}
|
|
// we locate the wad/pk3 name in the string and compare it to
|
|
// what we found on the previous iteration.
|
|
// if the name has changed, print it out on the screen
|
|
strcpy(tempstr, str);
|
|
str_ptr = strrchr(tempstr, '|');
|
|
if (str_ptr)
|
|
{
|
|
*str_ptr = '\0';
|
|
str = str_ptr + 1; // this is the name of the hook without the mod file name
|
|
str_ptr = strrchr(tempstr, PATHSEP[0]);
|
|
if (str_ptr)
|
|
tempstr = str_ptr + 1;
|
|
// tempstr should now point to the mod name, (wad/pk3) possibly truncated
|
|
if (strcmp(tempstr, last_mod_name) != 0)
|
|
{
|
|
strcpy(last_mod_name, tempstr);
|
|
len = (int)strlen(tempstr);
|
|
if (len > 25)
|
|
tempstr += len - 25;
|
|
snprintf(s, sizeof s - 1, "%s", tempstr);
|
|
V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
|
|
NEXT_ROW()
|
|
}
|
|
text_color = V_YELLOWMAP;
|
|
}
|
|
else
|
|
{
|
|
// probably a standalone lua file
|
|
// cut off the folder if it's there
|
|
str_ptr = strrchr(tempstr, PATHSEP[0]);
|
|
if (str_ptr)
|
|
str = str_ptr + 1;
|
|
text_color = 0; // white
|
|
}
|
|
len = (int)strlen(str);
|
|
if (len > 20)
|
|
str += len - 20;
|
|
snprintf(s, sizeof s - 1, "%20s: %d", str,
|
|
PS_GetMetricScreenValue(&hook[i].time_taken, true));
|
|
V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s);
|
|
NEXT_ROW()
|
|
|
|
#undef NEXT_ROW
|
|
|
|
}
|
|
}
|
|
|
|
static void PS_DrawPreThinkFrameStats(void)
|
|
{
|
|
draw_think_frame_stats(prethinkframe_hooks_length, prethinkframe_hooks);
|
|
}
|
|
|
|
static void PS_DrawThinkFrameStats(void)
|
|
{
|
|
draw_think_frame_stats(thinkframe_hooks_length, thinkframe_hooks);
|
|
}
|
|
|
|
static void PS_DrawPostThinkFrameStats(void)
|
|
{
|
|
draw_think_frame_stats(postthinkframe_hooks_length, postthinkframe_hooks);
|
|
}
|
|
|
|
void M_DrawPerfStats(void)
|
|
{
|
|
if (cv_perfstats.value == 1) // rendering
|
|
{
|
|
PS_UpdateFrameStats();
|
|
PS_DrawRenderStats();
|
|
}
|
|
else if (cv_perfstats.value == 2) // logic
|
|
{
|
|
// PS_UpdateTickStats is called in TryRunTics, since otherwise it would miss
|
|
// tics when frame skips happen
|
|
PS_DrawGameLogicStats();
|
|
}
|
|
else if (cv_perfstats.value >= 3) // lua thinkframe
|
|
{
|
|
if (!PS_IsLevelActive())
|
|
return;
|
|
if (!PS_HighResolution())
|
|
{
|
|
// Low resolutions can't really use V_DrawSmallString that is used by thinkframe stats.
|
|
// A low-res version using V_DrawThinString could be implemented,
|
|
// but it would have much less space for information.
|
|
V_DrawThinString(80, 92, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Lua Perfstats is not available");
|
|
V_DrawThinString(80, 100, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "for resolutions below 640x400.");
|
|
return;
|
|
}
|
|
if (cv_perfstats.value == 3)
|
|
{
|
|
PS_DrawThinkFrameStats();
|
|
}
|
|
else if (cv_perfstats.value == 4)
|
|
{
|
|
PS_DrawPreThinkFrameStats();
|
|
}
|
|
else if (cv_perfstats.value == 5)
|
|
{
|
|
PS_DrawPostThinkFrameStats();
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove and unallocate history from all metrics
|
|
static void PS_ClearHistory(void)
|
|
{
|
|
int i;
|
|
|
|
Z_FreeTag(PU_PERFSTATS);
|
|
// thinkframe hook metric history pointers need to be cleared manually
|
|
for (i = 0; i < thinkframe_hooks_length; i++)
|
|
{
|
|
thinkframe_hooks[i].time_taken.history = NULL;
|
|
}
|
|
|
|
ps_frame_index = ps_tick_index = 0;
|
|
// PS_UpdateMetricHistory will set these correctly when it runs
|
|
ps_frame_samples_left = ps_tick_samples_left = 0;
|
|
}
|
|
|
|
void PS_PerfStats_OnChange(void)
|
|
{
|
|
if (cv_perfstats.value && cv_ps_samplesize.value > 1)
|
|
PS_ClearHistory();
|
|
}
|
|
|
|
void PS_SampleSize_OnChange(void)
|
|
{
|
|
if (cv_ps_samplesize.value > 1)
|
|
PS_ClearHistory();
|
|
}
|