[Concept,12/14] expo: Add FPS tracking to test mode

Message ID 20251006205856.2009292-13-sjg@u-boot.org
State New
Headers
Series expo: More mouse development for expo |

Commit Message

Simon Glass Oct. 6, 2025, 8:58 p.m. UTC
  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(+)
  

Patch

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);