@@ -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
1617static const ImVec2 WINDOW_MIN_SIZE = {320 , 240 };
1718
1819static imgui_debug_time_history_t _imgui_debug_frame_perf_graph_history ;
1920static 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
244468void 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