mirror of
https://github.com/djhackersdev/bemanitools.git
synced 2025-02-26 22:49:28 +01:00
feat: Add frame time performance graph imgui overlay
A overlay window showing the a frame time graph with the current frame time, ~10 seconds of history as well as reference lines for the current avg. and a configurable target frame time. This is considered a debug tool to support in any efforts that are related to understanding current frame times of the games. More specifically this can be used to asses impact of any bemanitools hooking to the game’s main (render) loop.
This commit is contained in:
parent
1800f75f3d
commit
aa3e7d307d
@ -161,6 +161,7 @@ include src/main/iidxio/Module.mk
|
||||
include src/main/iidxiotest/Module.mk
|
||||
include src/main/imgui/Module.mk
|
||||
include src/main/imgui-bt/Module.mk
|
||||
include src/main/imgui-debug/Module.mk
|
||||
include src/main/inject/Module.mk
|
||||
include src/main/jbio-magicbox/Module.mk
|
||||
include src/main/jbio-p4io/Module.mk
|
||||
|
10
src/main/imgui-debug/Module.mk
Normal file
10
src/main/imgui-debug/Module.mk
Normal file
@ -0,0 +1,10 @@
|
||||
libs += imgui-debug
|
||||
|
||||
libs_imgui-debug := \
|
||||
imgui-bt \
|
||||
imgui \
|
||||
util \
|
||||
|
||||
src_imgui-debug := \
|
||||
frame-perf-graph.c \
|
||||
time-history.c \
|
258
src/main/imgui-debug/frame-perf-graph.c
Normal file
258
src/main/imgui-debug/frame-perf-graph.c
Normal file
@ -0,0 +1,258 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "imgui-bt/cimgui.h"
|
||||
|
||||
#include "imgui-debug/frame-perf-graph.h"
|
||||
#include "imgui-debug/time-history.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
typedef struct imgui_debug_frame_perf_graph {
|
||||
float target_time_ms;
|
||||
float y_axis_min_time_ms;
|
||||
float y_axis_max_time_ms;
|
||||
} imgui_debug_frame_perf_graph_t;
|
||||
|
||||
static const ImVec2 WINDOW_MIN_SIZE = {320, 240};
|
||||
|
||||
static imgui_debug_time_history_t _imgui_debug_frame_perf_graph_history;
|
||||
static imgui_debug_frame_perf_graph_t _imgui_debug_frame_perf_graph;
|
||||
|
||||
static void _imgui_debug_frame_perf_graph_draw(
|
||||
imgui_debug_frame_perf_graph_t *graph,
|
||||
const imgui_debug_time_history_t *history)
|
||||
{
|
||||
float current_value;
|
||||
ImVec2 window_size;
|
||||
static bool show_labels = true;
|
||||
static bool show_target_line = true;
|
||||
static bool show_avg_line = true;
|
||||
|
||||
log_assert(history);
|
||||
|
||||
current_value = imgui_debug_time_history_recent_value_get(history);
|
||||
|
||||
igSetNextWindowSize(WINDOW_MIN_SIZE, ImGuiCond_FirstUseEver);
|
||||
igSetNextWindowSizeConstraints(WINDOW_MIN_SIZE, igGetIO()->DisplaySize, NULL, NULL);
|
||||
|
||||
igBegin("Frame Time Graph", NULL, ImGuiWindowFlags_MenuBar);
|
||||
|
||||
// Add menu bar
|
||||
if (igBeginMenuBar()) {
|
||||
if (igBeginMenu("Settings", true)) {
|
||||
igPushItemWidth(110);
|
||||
|
||||
float min_time_slider = graph->y_axis_min_time_ms;
|
||||
float max_time_slider = graph->y_axis_max_time_ms;
|
||||
float target_time_input = graph->target_time_ms;
|
||||
|
||||
if (igDragFloat("y-axis min time (ms)", &min_time_slider, 0.1f, 0.1f, max_time_slider - 0.1f, "%.1f", ImGuiSliderFlags_None)) {
|
||||
graph->y_axis_min_time_ms = min_time_slider;
|
||||
}
|
||||
|
||||
if (igDragFloat("y-axis max time (ms)", &max_time_slider, 0.1f, min_time_slider + 0.1f, 100.0f, "%.1f", ImGuiSliderFlags_None)) {
|
||||
graph->y_axis_max_time_ms = max_time_slider;
|
||||
}
|
||||
|
||||
if (igInputFloat("Target time reference (ms)", &target_time_input, 0.0f, 0.0f, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (target_time_input >= 0.1f && target_time_input <= 100.0f) {
|
||||
graph->target_time_ms = target_time_input;
|
||||
} else {
|
||||
target_time_input = graph->target_time_ms;
|
||||
}
|
||||
}
|
||||
|
||||
igCheckbox("Show reference line labels", &show_labels);
|
||||
igCheckbox("Show target reference line", &show_target_line);
|
||||
igCheckbox("Show average reference line", &show_avg_line);
|
||||
|
||||
if (igButton("Focus on average", (ImVec2){0, 0})) {
|
||||
// Convert +/- 10 fps around average to milliseconds
|
||||
float avg_fps = 1000.0f / history->avg_time_ms;
|
||||
graph->y_axis_min_time_ms = 1000.0f / (avg_fps + 10.0f);
|
||||
graph->y_axis_max_time_ms = 1000.0f / fmaxf(avg_fps - 10.0f, 1.0f);
|
||||
}
|
||||
|
||||
igSameLine(0, -1);
|
||||
|
||||
if (igButton("Focus on target", (ImVec2){0, 0})) {
|
||||
// Convert +/- 10 fps around target to milliseconds
|
||||
float target_fps = 1000.0f / graph->target_time_ms;
|
||||
graph->y_axis_min_time_ms = 1000.0f / (target_fps + 10.0f);
|
||||
graph->y_axis_max_time_ms = 1000.0f / fmaxf(target_fps - 10.0f, 1.0f);
|
||||
}
|
||||
|
||||
igPopItemWidth();
|
||||
igEndMenu();
|
||||
}
|
||||
igEndMenuBar();
|
||||
}
|
||||
|
||||
igGetContentRegionAvail(&window_size);
|
||||
|
||||
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){1, 1, 0, 1});
|
||||
igText("Now %.3f ms", current_value);
|
||||
igPopStyleColor(1);
|
||||
igSameLine(0, -1);
|
||||
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){1, 0, 0, 1});
|
||||
igText("Target %.3f ms", graph->target_time_ms);
|
||||
igPopStyleColor(1);
|
||||
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){0, 1, 0, 1});
|
||||
igText("Avg %.3f ms", history->avg_time_ms);
|
||||
igPopStyleColor(1);
|
||||
igSameLine(0, -1);
|
||||
igText(" Min %.3f ms Max %.3f ms", history->min_time_ms, history->max_time_ms);
|
||||
|
||||
// Setup plot area using actual window size, with extra space at top for "ms" label
|
||||
ImVec2 plot_pos;
|
||||
igGetCursorScreenPos(&plot_pos);
|
||||
plot_pos.y += 15; // Add space at top for "ms" label
|
||||
ImVec2 plot_size = {window_size.x - 50, window_size.y - 65}; // Adjusted for extra top space
|
||||
|
||||
// Plot frame times in ms
|
||||
ImVec2 plot_pos_offset = {plot_pos.x + 50, plot_pos.y};
|
||||
igSetCursorScreenPos(plot_pos_offset);
|
||||
|
||||
igPlotLines_FloatPtr("##framegraph",
|
||||
history->time_values_ms,
|
||||
history->size,
|
||||
history->current_index,
|
||||
"",
|
||||
graph->y_axis_min_time_ms,
|
||||
graph->y_axis_max_time_ms,
|
||||
plot_size,
|
||||
sizeof(float));
|
||||
|
||||
// Draw Y axis (ms)
|
||||
ImDrawList* draw_list = igGetWindowDrawList();
|
||||
char y_label[32];
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, plot_pos.y},
|
||||
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), 1.0f);
|
||||
|
||||
// Draw "ms" label at top of y-axis
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y - 15},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "ms", NULL);
|
||||
|
||||
// Scale number of reference points based on plot height
|
||||
int num_reference_points = (int)(plot_size.y / 25); // One point per ~40 pixels
|
||||
if (num_reference_points < 4) num_reference_points = 4;
|
||||
|
||||
float time_min = graph->y_axis_min_time_ms;
|
||||
float time_max = graph->y_axis_max_time_ms;
|
||||
float time_step = (time_max - time_min) / (num_reference_points + 1);
|
||||
|
||||
// Draw min time value at top of y-axis and reference line
|
||||
snprintf(y_label, sizeof(y_label), "%.2f", time_min);
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y + plot_size.y - 10},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.3f}), 1.0f);
|
||||
|
||||
// Draw reference points and lines on side of y-axis
|
||||
for (int i = 1; i <= num_reference_points; i++) {
|
||||
float value = time_max - (time_step * i);
|
||||
float normalized_pos = (time_max - value) / (time_max - time_min);
|
||||
float y_pos = plot_pos.y + (plot_size.y * normalized_pos);
|
||||
snprintf(y_label, sizeof(y_label), "%.2f", value);
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, y_pos - 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, y_pos},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.2f}), 1.0f);
|
||||
}
|
||||
|
||||
// Draw max time value at bottom of y-axis and reference line
|
||||
snprintf(y_label, sizeof(y_label), "%.2f", time_max);
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, plot_pos.y},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.3f}), 1.0f);
|
||||
|
||||
// Draw target frame time reference line if within plot area
|
||||
if (show_target_line && graph->target_time_ms >= time_min && graph->target_time_ms <= time_max) {
|
||||
float normalized_target = (time_max - graph->target_time_ms) / (time_max - time_min);
|
||||
float y_pos_target = plot_pos.y + (plot_size.y * normalized_target);
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, y_pos_target},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos_target},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,0,0,1.0f}), 1.0f);
|
||||
if (show_labels) {
|
||||
snprintf(y_label, sizeof(y_label), "%.3f", graph->target_time_ms);
|
||||
ImDrawList_AddText_Vec2(draw_list,
|
||||
(ImVec2){plot_pos.x + 50 + plot_size.x/2 - 10, y_pos_target + 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,0,0,1}), y_label, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw reference line for current average if within plot area
|
||||
if (show_avg_line && history->avg_time_ms >= time_min && history->avg_time_ms <= time_max) {
|
||||
float normalized_avg = (time_max - history->avg_time_ms) / (time_max - time_min);
|
||||
float y_pos_avg = plot_pos.y + (plot_size.y * normalized_avg);
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, y_pos_avg},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos_avg},
|
||||
igColorConvertFloat4ToU32((ImVec4){0,1,0,1.0f}), 1.0f);
|
||||
if (show_labels) {
|
||||
snprintf(y_label, sizeof(y_label), "%.3f", history->avg_time_ms);
|
||||
ImDrawList_AddText_Vec2(draw_list,
|
||||
(ImVec2){plot_pos.x + 50 + plot_size.x/2 - 10, y_pos_avg + 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){0,1,0,1}), y_label, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw X axis (time in frames)
|
||||
ImDrawList_AddLine(draw_list,
|
||||
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
|
||||
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), 1.0f);
|
||||
|
||||
// Draw "frames" label centered on x-axis
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 50 + (plot_size.x / 2) - 20, plot_pos.y + plot_size.y + 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "frames ago", NULL);
|
||||
|
||||
char x_label[32];
|
||||
snprintf(x_label, sizeof(x_label), "%d", history->size);
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y + 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), x_label, NULL);
|
||||
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y + 5},
|
||||
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "0", NULL);
|
||||
|
||||
igEnd();
|
||||
}
|
||||
|
||||
static void _imgui_debug_frame_perf_graph_component_frame_update(ImGuiContext *ctx)
|
||||
{
|
||||
ImGuiIO *io;
|
||||
|
||||
log_assert(ctx);
|
||||
|
||||
igSetCurrentContext(ctx);
|
||||
io = igGetIO();
|
||||
|
||||
imgui_debug_time_history_update(&_imgui_debug_frame_perf_graph_history, 1000.0f / io->Framerate);
|
||||
|
||||
_imgui_debug_frame_perf_graph_draw(&_imgui_debug_frame_perf_graph, &_imgui_debug_frame_perf_graph_history);
|
||||
}
|
||||
|
||||
void imgui_debug_frame_perf_graph_init(
|
||||
float target_fps,
|
||||
imgui_bt_component_t *component)
|
||||
{
|
||||
log_assert(target_fps > 0.0f);
|
||||
log_assert(component);
|
||||
|
||||
imgui_debug_time_history_init(ceilf(10 * target_fps), &_imgui_debug_frame_perf_graph_history);
|
||||
|
||||
_imgui_debug_frame_perf_graph.target_time_ms = 1000.0f / target_fps;
|
||||
_imgui_debug_frame_perf_graph.y_axis_min_time_ms = 1000.0f / fmaxf(0.0f, target_fps - 20.0f);
|
||||
_imgui_debug_frame_perf_graph.y_axis_max_time_ms = 1000.0f / fminf(target_fps + 20.0f, 1000.0f);
|
||||
|
||||
component->frame_update = _imgui_debug_frame_perf_graph_component_frame_update;
|
||||
}
|
10
src/main/imgui-debug/frame-perf-graph.h
Normal file
10
src/main/imgui-debug/frame-perf-graph.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef IMGUI_DEBUG_FRAME_PERF_GRAPH_H
|
||||
#define IMGUI_DEBUG_FRAME_PERF_GRAPH_H
|
||||
|
||||
#include "imgui-bt/component.h"
|
||||
|
||||
void imgui_debug_frame_perf_graph_init(
|
||||
float target_fps,
|
||||
imgui_bt_component_t *component);
|
||||
|
||||
#endif
|
63
src/main/imgui-debug/time-history.c
Normal file
63
src/main/imgui-debug/time-history.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "time-history.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/mem.h"
|
||||
|
||||
void imgui_debug_time_history_init(uint32_t size, imgui_debug_time_history_t *history)
|
||||
{
|
||||
log_assert(size > 0);
|
||||
log_assert(history);
|
||||
|
||||
memset(history, 0, sizeof(imgui_debug_time_history_t));
|
||||
|
||||
history->size = size;
|
||||
history->time_values_ms = (float *) xmalloc(size * sizeof(float));
|
||||
}
|
||||
|
||||
void imgui_debug_time_history_update(imgui_debug_time_history_t *history, float time_ms)
|
||||
{
|
||||
log_assert(history);
|
||||
|
||||
history->time_values_ms[history->current_index] = time_ms;
|
||||
history->current_index = (history->current_index + 1) % history->size;
|
||||
|
||||
history->min_time_ms = history->time_values_ms[0];
|
||||
history->max_time_ms = history->time_values_ms[0];
|
||||
history->avg_time_ms = 0;
|
||||
|
||||
for (uint32_t i = 0; i < history->size; i++) {
|
||||
if (history->time_values_ms[i] < history->min_time_ms) {
|
||||
history->min_time_ms = history->time_values_ms[i];
|
||||
}
|
||||
|
||||
if (history->time_values_ms[i] > history->max_time_ms) {
|
||||
history->max_time_ms = history->time_values_ms[i];
|
||||
}
|
||||
|
||||
history->avg_time_ms += history->time_values_ms[i];
|
||||
}
|
||||
|
||||
history->avg_time_ms /= history->size;
|
||||
}
|
||||
|
||||
float imgui_debug_time_history_recent_value_get(const imgui_debug_time_history_t *history)
|
||||
{
|
||||
log_assert(history);
|
||||
|
||||
if (history->current_index == 0) {
|
||||
return history->time_values_ms[history->size - 1];
|
||||
} else {
|
||||
return history->time_values_ms[history->current_index - 1];
|
||||
}
|
||||
}
|
||||
|
||||
void imgui_debug_time_history_free(imgui_debug_time_history_t *history)
|
||||
{
|
||||
log_assert(history);
|
||||
log_assert(history->time_values_ms);
|
||||
|
||||
free(history->time_values_ms);
|
||||
}
|
23
src/main/imgui-debug/time-history.h
Normal file
23
src/main/imgui-debug/time-history.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef IMGUI_DEBUG_TIME_HISTORY_H
|
||||
#define IMGUI_DEBUG_TIME_HISTORY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct imgui_debug_time_history {
|
||||
uint32_t size;
|
||||
float *time_values_ms;
|
||||
uint32_t current_index;
|
||||
float min_time_ms;
|
||||
float max_time_ms;
|
||||
float avg_time_ms;
|
||||
} imgui_debug_time_history_t;
|
||||
|
||||
void imgui_debug_time_history_init(uint32_t size, imgui_debug_time_history_t *history);
|
||||
|
||||
void imgui_debug_time_history_update(imgui_debug_time_history_t *history, float time_ms);
|
||||
|
||||
float imgui_debug_time_history_recent_value_get(const imgui_debug_time_history_t *history);
|
||||
|
||||
void imgui_debug_time_history_free(imgui_debug_time_history_t *history);
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user