Files
u-boot/boot/expo_test.c
Simon Glass aa83cf00ce expo: Expand timing to include render and poll
Provide more timing information for expo operation, including the time
taken to:

- render the scene
- sync the framebuffer to the display
- poll for keyboard / mouse input

These are averages calculated over the past second.

Update the documentation to mention these features.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-10-07 10:54:13 +00:00

218 lines
5.5 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Expo test mode
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <dm.h>
#include <env.h>
#include <errno.h>
#include <expo.h>
#include <expo_test.h>
#include <log.h>
#include <malloc.h>
#include <time.h>
#include <video.h>
#include <video_console.h>
int expo_test_init(struct expo *exp)
{
struct expo_test_mode *test;
test = calloc(1, sizeof(struct expo_test_mode));
if (!test)
return log_msg_ret("test", -ENOMEM);
exp->test = test;
expo_test_checkenv(exp);
return 0;
}
void expo_test_uninit(struct expo *exp)
{
free(exp->test);
exp->test = NULL;
}
void expo_test_checkenv(struct expo *exp)
{
struct expo_test_mode *test = exp->test;
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_mark(struct expo *exp)
{
struct expo_test_mode *test = exp->test;
test->base_time_us = timer_get_us();
}
void expo_test_update(struct expo *exp)
{
struct expo_test_mode *test = exp->test;
test->render_count++;
}
void expo_test_sync(struct expo *exp)
{
struct expo_test_mode *test = exp->test;
test->sync_delta_us = get_timer_us(test->base_time_us);
}
void expo_test_poll(struct expo *exp)
{
struct expo_test_mode *test = exp->test;
test->poll_delta_us = get_timer_us(test->base_time_us);
}
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;
struct vidconsole_priv *cons_priv;
struct udevice *dev = exp->display;
struct video_priv *vid_priv;
char buf[30];
ulong now;
int x, y;
int ret;
if (!test->enabled)
return 0;
/* Calculate time between update and render */
if (test->base_time_us)
test->render_delta_us = get_timer_us(test->base_time_us);
/* Select 8x16 font for test display */
ret = vidconsole_select_font(exp->cons, "8x16", 0);
if (ret && ret != -ENOSYS)
return log_msg_ret("font", ret);
vid_priv = dev_get_uclass_priv(dev);
cons_priv = dev_get_uclass_priv(exp->cons);
/* Accumulate delta times for averaging */
test->render_total_us += test->render_delta_us;
test->sync_total_us += test->sync_delta_us;
test->poll_total_us += test->poll_delta_us;
test->frame_count_last_sec++;
/* Update FPS and averages 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);
/* Calculate averages over the last second */
if (test->frame_count_last_sec > 0) {
test->render_avg_us = test->render_total_us /
test->frame_count_last_sec;
test->sync_avg_us = test->sync_total_us /
test->frame_count_last_sec;
test->poll_avg_us = test->poll_total_us /
test->frame_count_last_sec;
}
/* Reset accumulation counters */
test->render_total_us = 0;
test->sync_total_us = 0;
test->poll_total_us = 0;
test->frame_count_last_sec = 0;
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;
y = 10;
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);
}
/* Display average render time in milliseconds on next line */
snprintf(buf, sizeof(buf), "render %6lu.%01lums",
test->render_avg_us / 1000,
(test->render_avg_us % 1000) / 100);
y += cons_priv->y_charsize;
vidconsole_set_cursor_pos(exp->cons, x, y);
vidconsole_put_string(exp->cons, buf);
/* Display average sync time in milliseconds on next line */
snprintf(buf, sizeof(buf), "sync %6lu.%01lums",
test->sync_avg_us / 1000,
(test->sync_avg_us % 1000) / 100);
y += cons_priv->y_charsize;
vidconsole_set_cursor_pos(exp->cons, x, y);
vidconsole_put_string(exp->cons, buf);
/* Display average poll time in milliseconds on next line */
snprintf(buf, sizeof(buf), "poll %6lu.%01lums",
test->poll_avg_us / 1000,
(test->poll_avg_us % 1000) / 100);
y += cons_priv->y_charsize;
vidconsole_set_cursor_pos(exp->cons, x, y);
vidconsole_put_string(exp->cons, buf);
return 0;
}