From: Simon Glass <sjg@chromium.org> In test mode, show the FPS (frames per second) below the frame count. This is helpful for performance monitoring during development. The FPS calculation averages over the last 5 seconds to provide a stable reading. Add a test for the FPS calculation logic as well. Mention expo's test mode in the documentation. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo_test.c | 60 ++++++++++++++++++++++++++++++++++++++++++++ doc/develop/expo.rst | 16 ++++++++++++ include/expo_test.h | 28 +++++++++++++++++++++ test/boot/expo.c | 57 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) diff --git a/boot/expo_test.c b/boot/expo_test.c index 0ac2d0551a1..726027aabb6 100644 --- a/boot/expo_test.c +++ b/boot/expo_test.c @@ -15,6 +15,7 @@ #include <expo_test.h> #include <log.h> #include <malloc.h> +#include <time.h> #include <video.h> #include <video_console.h> @@ -44,6 +45,8 @@ void expo_test_checkenv(struct expo *exp) test->enabled = env_get_yesno("expotest") == 1; test->render_count = 0; + test->start_time_ms = get_timer(0); + test->last_update = get_timer(0); } void expo_test_update(struct expo *exp) @@ -53,6 +56,44 @@ void expo_test_update(struct expo *exp) test->render_count++; } +int expo_calc_fps(struct expo_test_mode *test) +{ + ulong oldest_time, newest_time; + int oldest_frames, newest_frames; + int frame_delta, time_delta; + int oldest_idx; + int fps; + + /* Use most recent entry */ + newest_time = test->fps_timestamps_ms[test->fps_index]; + newest_frames = test->fps_frame_counts[test->fps_index]; + + /* Find oldest valid entry by looking backwards from current index */ + oldest_idx = (test->fps_index + 1) % EXPO_FPS_AVG_SECONDS; + if (test->fps_timestamps_ms[oldest_idx] == 0) { + /* Array hasn't wrapped yet, use first entry */ + oldest_idx = 0; + } + + oldest_time = test->fps_timestamps_ms[oldest_idx]; + oldest_frames = test->fps_frame_counts[oldest_idx]; + + /* Need at least two data points with different timestamps */ + if (oldest_time >= newest_time) + return 0; + + frame_delta = newest_frames - oldest_frames; + time_delta = newest_time - oldest_time; + + if (!time_delta) + return 0; + + /* Calculate FPS: frames / (time_ms / 1000) */ + fps = (frame_delta * 1000) / time_delta; + + return fps; +} + int expo_test_render(struct expo *exp) { struct expo_test_mode *test = exp->test; @@ -60,6 +101,7 @@ int expo_test_render(struct expo *exp) struct udevice *dev = exp->display; struct video_priv *vid_priv; char buf[30]; + ulong now; int x, y; int ret; @@ -74,6 +116,16 @@ int expo_test_render(struct expo *exp) vid_priv = dev_get_uclass_priv(dev); cons_priv = dev_get_uclass_priv(exp->cons); + /* Update FPS if at least 1 second has elapsed */ + if (get_timer(test->last_update) >= 1000) { + now = get_timer(test->start_time_ms); + test->fps_index = (test->fps_index + 1) % EXPO_FPS_AVG_SECONDS; + test->fps_timestamps_ms[test->fps_index] = now; + test->fps_frame_counts[test->fps_index] = test->render_count; + test->fps_last = expo_calc_fps(test); + test->last_update = get_timer(0); + } + /* Display frame count */ snprintf(buf, sizeof(buf), "frame %6d", test->render_count); x = vid_priv->xsize - 18 * cons_priv->x_charsize; @@ -81,5 +133,13 @@ int expo_test_render(struct expo *exp) vidconsole_set_cursor_pos(exp->cons, x, y); vidconsole_put_string(exp->cons, buf); + /* Display FPS on next line (only if non-zero) */ + if (test->fps_last > 0) { + snprintf(buf, sizeof(buf), "fps %6d", test->fps_last); + y += cons_priv->y_charsize; + vidconsole_set_cursor_pos(exp->cons, x, y); + vidconsole_put_string(exp->cons, buf); + } + return 0; } diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 5c2e3157c8f..85ccbe7fd63 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -577,6 +577,22 @@ API documentation Future ideas ------------ +Test Mode +--------- + +Expo supports a test mode that can be enabled by setting the environment +variable `expotest` to 1. When enabled, expo displays the frame count in the +top-right corner of the display. This is useful for debugging and performance +analysis. + +To enable test mode:: + + => setenv expotest 1 + => bootflow menu + +The frame count shows the number of times `expo_render()` has been called since +`expo_enter_mode()` was invoked. The counter resets each time expo mode is entered. + Some ideas for future work: - Default menu item and a timeout diff --git a/include/expo_test.h b/include/expo_test.h index 88cec4a2a96..9d557e29c87 100644 --- a/include/expo_test.h +++ b/include/expo_test.h @@ -9,15 +9,30 @@ struct expo; +/* Number of seconds to average FPS over in test mode */ +#define EXPO_FPS_AVG_SECONDS 5 + /** * struct expo_test_mode - Test mode information for expo * * @enabled: true if test mode is enabled + * @start_time_ms: Time when expo_enter_mode() was called (milliseconds) * @render_count: Number of calls to expo_render() since expo_enter_mode() + * @fps_timestamps_ms: Timestamps for FPS calculation (milliseconds) + * @fps_frame_counts: Frame counts at each timestamp + * @fps_index: Current index in the FPS tracking arrays + * @fps_last: Last calculated FPS value + * @last_update: Time of last FPS update (milliseconds) */ struct expo_test_mode { bool enabled; + ulong start_time_ms; int render_count; + ulong fps_timestamps_ms[EXPO_FPS_AVG_SECONDS]; + int fps_frame_counts[EXPO_FPS_AVG_SECONDS]; + int fps_index; + int fps_last; + ulong last_update; }; #if CONFIG_IS_ENABLED(EXPO_TEST) @@ -62,6 +77,14 @@ void expo_test_update(struct expo *exp); */ int expo_test_render(struct expo *exp); +/** + * expo_calc_fps() - Calculate FPS based on recent frame history + * + * @test: Test mode data containing frame history + * Return: Calculated FPS value, or 0 if insufficient data + */ +int expo_calc_fps(struct expo_test_mode *test); + #else static inline int expo_test_init(struct expo *exp) @@ -86,6 +109,11 @@ static inline int expo_test_render(struct expo *exp) return 0; } +static inline int expo_calc_fps(struct expo_test_mode *test) +{ + return 0; +} + #endif /* EXPO_TEST */ #endif /* __EXPO_TEST_H */ diff --git a/test/boot/expo.c b/test/boot/expo.c index a1aa543ab71..8a401ba9884 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1163,3 +1163,60 @@ static int expo_test_mode(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(expo_test_mode, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +static int expo_test_calc_fps(struct unit_test_state *uts) +{ + struct expo_test_mode test; + int fps; + + memset(&test, 0, sizeof(test)); + + /* No data - should return 0 */ + fps = expo_calc_fps(&test); + ut_asserteq(0, fps); + + /* Single data point - should return 0 */ + test.fps_index = 0; + test.fps_timestamps_ms[0] = 0; + test.fps_frame_counts[0] = 0; + fps = expo_calc_fps(&test); + ut_asserteq(0, fps); + + /* Two data points: 100 frames in 1000ms = 100 FPS */ + test.fps_index = 1; + test.fps_timestamps_ms[0] = 0; + test.fps_frame_counts[0] = 0; + test.fps_timestamps_ms[1] = 1000; + test.fps_frame_counts[1] = 100; + fps = expo_calc_fps(&test); + ut_asserteq(100, fps); + + /* Three data points spanning 2 seconds: 240 frames in 2000ms = 120 FPS */ + test.fps_index = 2; + test.fps_timestamps_ms[0] = 0; + test.fps_frame_counts[0] = 0; + test.fps_timestamps_ms[1] = 1000; + test.fps_frame_counts[1] = 100; + test.fps_timestamps_ms[2] = 2000; + test.fps_frame_counts[2] = 240; + fps = expo_calc_fps(&test); + ut_asserteq(120, fps); + + /* Test wraparound: index at 1, with data at indices 2,3,4,0,1 */ + test.fps_index = 1; + test.fps_timestamps_ms[2] = 0; + test.fps_frame_counts[2] = 0; + test.fps_timestamps_ms[3] = 1000; + test.fps_frame_counts[3] = 60; + test.fps_timestamps_ms[4] = 2000; + test.fps_frame_counts[4] = 120; + test.fps_timestamps_ms[0] = 3000; + test.fps_frame_counts[0] = 180; + test.fps_timestamps_ms[1] = 4000; + test.fps_frame_counts[1] = 240; + fps = expo_calc_fps(&test); + ut_asserteq(60, fps); /* 240 frames in 4000ms = 60 FPS */ + + return 0; +} +BOOTSTD_TEST(expo_test_calc_fps, 0); -- 2.43.0