Skip to content

Commit e0ff83f

Browse files
icex2icex2
andauthored
feat: d3d9-frame-graph-hook, add frame rate graph view (#330)
Have a checkbox to switch to a frame graph focused view which saves a bunch of math when it’s more useful to focus on the frame/refresh rate than the frame time values. Co-authored-by: icex2 <djh.icex2@gmail.com>
1 parent 972acb5 commit e0ff83f

1 file changed

Lines changed: 227 additions & 2 deletions

File tree

src/main/imgui-debug/frame-perf-graph.c

Lines changed: 227 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,232 @@ typedef struct imgui_debug_frame_perf_graph {
1111
float target_time_ms;
1212
float y_axis_min_time_ms;
1313
float y_axis_max_time_ms;
14+
bool show_frame_rate_graph;
1415
} imgui_debug_frame_perf_graph_t;
1516

1617
static const ImVec2 WINDOW_MIN_SIZE = {320, 240};
1718

1819
static imgui_debug_time_history_t _imgui_debug_frame_perf_graph_history;
1920
static imgui_debug_frame_perf_graph_t _imgui_debug_frame_perf_graph;
2021

21-
static void _imgui_debug_frame_perf_graph_draw(
22+
static void _imgui_debug_frame_perf_frame_rate_graph_draw(
23+
imgui_debug_frame_perf_graph_t *graph,
24+
const imgui_debug_time_history_t *history)
25+
{
26+
float current_value;
27+
ImVec2 window_size;
28+
static bool show_labels = true;
29+
static bool show_target_line = true;
30+
static bool show_avg_line = true;
31+
32+
log_assert(history);
33+
34+
current_value = 1000.0f / imgui_debug_time_history_recent_value_get(history);
35+
36+
igSetNextWindowSize(WINDOW_MIN_SIZE, ImGuiCond_FirstUseEver);
37+
igSetNextWindowSizeConstraints(WINDOW_MIN_SIZE, igGetIO()->DisplaySize, NULL, NULL);
38+
39+
igBegin("Frame Rate Graph", NULL, ImGuiWindowFlags_MenuBar);
40+
41+
// Add menu bar
42+
if (igBeginMenuBar()) {
43+
if (igBeginMenu("Settings", true)) {
44+
igPushItemWidth(110);
45+
46+
float min_fps = 1000.0f / graph->y_axis_max_time_ms;
47+
float max_fps = 1000.0f / graph->y_axis_min_time_ms;
48+
float target_fps = 1000.0f / graph->target_time_ms;
49+
50+
if (igDragFloat("y-axis min FPS", &min_fps, 1.0f, 1.0f, max_fps - 1.0f, "%.1f", ImGuiSliderFlags_None)) {
51+
graph->y_axis_max_time_ms = 1000.0f / min_fps;
52+
}
53+
54+
if (igDragFloat("y-axis max FPS", &max_fps, 1.0f, min_fps + 1.0f, 1000.0f, "%.1f", ImGuiSliderFlags_None)) {
55+
graph->y_axis_min_time_ms = 1000.0f / max_fps;
56+
}
57+
58+
if (igInputFloat("Target FPS", &target_fps, 0.0f, 0.0f, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue)) {
59+
if (target_fps >= 1.0f && target_fps <= 1000.0f) {
60+
graph->target_time_ms = 1000.0f / target_fps;
61+
} else {
62+
target_fps = 1000.0f / graph->target_time_ms;
63+
}
64+
}
65+
66+
igCheckbox("Show reference line labels", &show_labels);
67+
igCheckbox("Show target reference line", &show_target_line);
68+
igCheckbox("Show average reference line", &show_avg_line);
69+
70+
if (igButton("Focus on average", (ImVec2){0, 0})) {
71+
float avg_fps = 1000.0f / history->avg_time_ms;
72+
graph->y_axis_min_time_ms = 1000.0f / (avg_fps + 10.0f);
73+
graph->y_axis_max_time_ms = 1000.0f / fmaxf(avg_fps - 10.0f, 1.0f);
74+
}
75+
76+
igSameLine(0, -1);
77+
78+
if (igButton("Focus on target", (ImVec2){0, 0})) {
79+
float target_fps = 1000.0f / graph->target_time_ms;
80+
graph->y_axis_min_time_ms = 1000.0f / (target_fps + 10.0f);
81+
graph->y_axis_max_time_ms = 1000.0f / fmaxf(target_fps - 10.0f, 1.0f);
82+
}
83+
84+
igCheckbox("Show frame rate graph", &graph->show_frame_rate_graph);
85+
86+
igPopItemWidth();
87+
igEndMenu();
88+
}
89+
igEndMenuBar();
90+
}
91+
92+
igGetContentRegionAvail(&window_size);
93+
94+
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){1, 1, 0, 1});
95+
igText("Now %.3f FPS", current_value);
96+
igPopStyleColor(1);
97+
igSameLine(0, -1);
98+
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){1, 0, 0, 1});
99+
igText("Target %.3f FPS", 1000.0f / graph->target_time_ms);
100+
igPopStyleColor(1);
101+
igPushStyleColor_Vec4(ImGuiCol_Text, (ImVec4){0, 1, 0, 1});
102+
igText("Avg %.3f FPS", 1000.0f / history->avg_time_ms);
103+
igPopStyleColor(1);
104+
igSameLine(0, -1);
105+
igText(" Min %.3f FPS Max %.3f FPS", 1000.0f / history->max_time_ms, 1000.0f / history->min_time_ms);
106+
107+
// Setup plot area using actual window size, with extra space at top for "FPS" label
108+
ImVec2 plot_pos;
109+
igGetCursorScreenPos(&plot_pos);
110+
plot_pos.y += 15; // Add space at top for "FPS" label
111+
ImVec2 plot_size = {window_size.x - 50, window_size.y - 65}; // Adjusted for extra top space
112+
113+
// Convert time values to FPS for plotting
114+
float fps_values[history->size];
115+
for(int i = 0; i < history->size; i++) {
116+
fps_values[i] = 1000.0f / history->time_values_ms[i];
117+
}
118+
119+
// Plot FPS values
120+
ImVec2 plot_pos_offset = {plot_pos.x + 50, plot_pos.y};
121+
igSetCursorScreenPos(plot_pos_offset);
122+
123+
igPlotLines_FloatPtr("##framegraph",
124+
fps_values,
125+
history->size,
126+
history->current_index,
127+
"",
128+
1000.0f / graph->y_axis_min_time_ms, // Swapped min/max to invert Y axis
129+
1000.0f / graph->y_axis_max_time_ms,
130+
plot_size,
131+
sizeof(float));
132+
133+
// Draw Y axis (FPS)
134+
ImDrawList* draw_list = igGetWindowDrawList();
135+
char y_label[32];
136+
ImDrawList_AddLine(draw_list,
137+
(ImVec2){plot_pos.x + 50, plot_pos.y},
138+
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
139+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), 1.0f);
140+
141+
// Draw "FPS" label at top of y-axis
142+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y - 15},
143+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "FPS", NULL);
144+
145+
// Scale number of reference points based on plot height
146+
int num_reference_points = (int)(plot_size.y / 25); // One point per ~40 pixels
147+
if (num_reference_points < 4) num_reference_points = 4;
148+
149+
float fps_min = 1000.0f / graph->y_axis_max_time_ms;
150+
float fps_max = 1000.0f / graph->y_axis_min_time_ms;
151+
float fps_step = (fps_max - fps_min) / (num_reference_points + 1);
152+
153+
// Draw min FPS value at bottom of y-axis and reference line
154+
snprintf(y_label, sizeof(y_label), "%.3f", fps_min);
155+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y + plot_size.y - 10},
156+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
157+
ImDrawList_AddLine(draw_list,
158+
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
159+
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y},
160+
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.3f}), 1.0f);
161+
162+
// Draw reference points and lines on side of y-axis
163+
for (int i = 1; i <= num_reference_points; i++) {
164+
float value = fps_min + (fps_step * i);
165+
float normalized_pos = 1.0f - ((value - fps_min) / (fps_max - fps_min)); // Inverted normalization
166+
float y_pos = plot_pos.y + (plot_size.y * normalized_pos);
167+
snprintf(y_label, sizeof(y_label), "%.3f", value);
168+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, y_pos - 5},
169+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
170+
ImDrawList_AddLine(draw_list,
171+
(ImVec2){plot_pos.x + 50, y_pos},
172+
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos},
173+
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.2f}), 1.0f);
174+
}
175+
176+
// Draw max FPS value at top of y-axis and reference line
177+
snprintf(y_label, sizeof(y_label), "%.3f", fps_max);
178+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 5, plot_pos.y},
179+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), y_label, NULL);
180+
ImDrawList_AddLine(draw_list,
181+
(ImVec2){plot_pos.x + 50, plot_pos.y},
182+
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y},
183+
igColorConvertFloat4ToU32((ImVec4){1,1,1,0.3f}), 1.0f);
184+
185+
// Draw target FPS reference line if within plot area
186+
float target_fps_value = 1000.0f / graph->target_time_ms;
187+
if (show_target_line && target_fps_value >= fps_min && target_fps_value <= fps_max) {
188+
float normalized_target = 1.0f - ((target_fps_value - fps_min) / (fps_max - fps_min)); // Inverted normalization
189+
float y_pos_target = plot_pos.y + (plot_size.y * normalized_target);
190+
ImDrawList_AddLine(draw_list,
191+
(ImVec2){plot_pos.x + 50, y_pos_target},
192+
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos_target},
193+
igColorConvertFloat4ToU32((ImVec4){1,0,0,1.0f}), 1.0f);
194+
if (show_labels) {
195+
snprintf(y_label, sizeof(y_label), "%.3f", target_fps_value);
196+
ImDrawList_AddText_Vec2(draw_list,
197+
(ImVec2){plot_pos.x + 50 + plot_size.x/2 - 10, y_pos_target + 5},
198+
igColorConvertFloat4ToU32((ImVec4){1,0,0,1}), y_label, NULL);
199+
}
200+
}
201+
202+
// Draw reference line for current average if within plot area
203+
float avg_fps = 1000.0f / history->avg_time_ms;
204+
if (show_avg_line && avg_fps >= fps_min && avg_fps <= fps_max) {
205+
float normalized_avg = 1.0f - ((avg_fps - fps_min) / (fps_max - fps_min)); // Inverted normalization
206+
float y_pos_avg = plot_pos.y + (plot_size.y * normalized_avg);
207+
ImDrawList_AddLine(draw_list,
208+
(ImVec2){plot_pos.x + 50, y_pos_avg},
209+
(ImVec2){plot_pos.x + plot_size.x + 50, y_pos_avg},
210+
igColorConvertFloat4ToU32((ImVec4){0,1,0,1.0f}), 1.0f);
211+
if (show_labels) {
212+
snprintf(y_label, sizeof(y_label), "%.3f", avg_fps);
213+
ImDrawList_AddText_Vec2(draw_list,
214+
(ImVec2){plot_pos.x + 50 + plot_size.x/2 - 10, y_pos_avg + 5},
215+
igColorConvertFloat4ToU32((ImVec4){0,1,0,1}), y_label, NULL);
216+
}
217+
}
218+
219+
// Draw X axis (time in frames)
220+
ImDrawList_AddLine(draw_list,
221+
(ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y},
222+
(ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y},
223+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), 1.0f);
224+
225+
// Draw "frames" label centered on x-axis
226+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 50 + (plot_size.x / 2) - 20, plot_pos.y + plot_size.y + 5},
227+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "frames ago", NULL);
228+
229+
char x_label[32];
230+
snprintf(x_label, sizeof(x_label), "%d", history->size);
231+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + 50, plot_pos.y + plot_size.y + 5},
232+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), x_label, NULL);
233+
ImDrawList_AddText_Vec2(draw_list, (ImVec2){plot_pos.x + plot_size.x + 50, plot_pos.y + plot_size.y + 5},
234+
igColorConvertFloat4ToU32((ImVec4){1,1,1,1}), "0", NULL);
235+
236+
igEnd();
237+
}
238+
239+
static void _imgui_debug_frame_perf_frame_time_graph_draw(
22240
imgui_debug_frame_perf_graph_t *graph,
23241
const imgui_debug_time_history_t *history)
24242
{
@@ -82,6 +300,8 @@ static void _imgui_debug_frame_perf_graph_draw(
82300
graph->y_axis_max_time_ms = 1000.0f / fmaxf(target_fps - 10.0f, 1.0f);
83301
}
84302

303+
igCheckbox("Show frame rate graph", &graph->show_frame_rate_graph);
304+
85305
igPopItemWidth();
86306
igEndMenu();
87307
}
@@ -238,7 +458,11 @@ static void _imgui_debug_frame_perf_graph_component_frame_update(ImGuiContext *c
238458

239459
imgui_debug_time_history_update(&_imgui_debug_frame_perf_graph_history, 1000.0f / io->Framerate);
240460

241-
_imgui_debug_frame_perf_graph_draw(&_imgui_debug_frame_perf_graph, &_imgui_debug_frame_perf_graph_history);
461+
if (_imgui_debug_frame_perf_graph.show_frame_rate_graph) {
462+
_imgui_debug_frame_perf_frame_rate_graph_draw(&_imgui_debug_frame_perf_graph, &_imgui_debug_frame_perf_graph_history);
463+
} else {
464+
_imgui_debug_frame_perf_frame_time_graph_draw(&_imgui_debug_frame_perf_graph, &_imgui_debug_frame_perf_graph_history);
465+
}
242466
}
243467

244468
void imgui_debug_frame_perf_graph_init(
@@ -253,6 +477,7 @@ void imgui_debug_frame_perf_graph_init(
253477
_imgui_debug_frame_perf_graph.target_time_ms = 1000.0f / target_fps;
254478
_imgui_debug_frame_perf_graph.y_axis_min_time_ms = 1000.0f / fmaxf(0.0f, target_fps - 20.0f);
255479
_imgui_debug_frame_perf_graph.y_axis_max_time_ms = 1000.0f / fminf(target_fps + 20.0f, 1000.0f);
480+
_imgui_debug_frame_perf_graph.show_frame_rate_graph = false;
256481

257482
component->frame_update = _imgui_debug_frame_perf_graph_component_frame_update;
258483
}

0 commit comments

Comments
 (0)