[PATCH 00/14] expo: More mouse development for expo
From: Simon Glass <sjg@chromium.org> This series adds support for hiding the system pointer, if one is available, since it is confusing to have an expo pointer separate from the system one. It als introduces a simple test mode for expo, to check the frame rate, etc. Some minor tweaks in the video uclass are also included, as well as improvements to the video documentation. Simon Glass (14): video: Do the sync timing within video_sync() video: Add a little more documentation to the uclass mouse: Add comment for mouse_get_event() mouse: Add method to show/hide the system pointer mouse: Add support for scaling of video-device coordinates sandbox: mouse: Implement set_ptr_visible() sandbox: mouse: Add test for pointer visibility expo: Hide system pointer when entering expo mode expo: Introduce a test mode expo: Track the number of render calls expo: Add test mode to display frame count expo: Add FPS tracking to test mode expo: Expand timing to include render and poll expo: Use manual-sync mode arch/sandbox/cpu/sdl.c | 5 + arch/sandbox/include/asm/sdl.h | 11 ++ arch/sandbox/include/asm/test.h | 8 ++ boot/Kconfig | 10 ++ boot/Makefile | 1 + boot/expo.c | 44 ++++++- boot/expo_test.c | 217 ++++++++++++++++++++++++++++++++ doc/develop/expo.rst | 36 ++++++ drivers/input/mouse-uclass.c | 27 ++++ drivers/input/sandbox_mouse.c | 33 +++++ drivers/video/video-uclass.c | 44 +++++-- include/expo.h | 4 + include/expo_test.h | 181 ++++++++++++++++++++++++++ include/mouse.h | 66 ++++++++++ test/boot/expo.c | 123 ++++++++++++++++++ test/dm/mouse.c | 22 ++++ 16 files changed, 818 insertions(+), 14 deletions(-) create mode 100644 boot/expo_test.c create mode 100644 include/expo_test.h -- 2.43.0 base-commit: a302031398392259aebbcd3103e03a57b478c0a4 branch: proe
From: Simon Glass <sjg@chromium.org> The timer is used in a different function from that where it is set up. Move setup into video_sync() so that the code is all together. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/video-uclass.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 500a04a0442..37171f6b2da 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -519,11 +519,6 @@ int video_manual_sync(struct udevice *vid, uint flags) video_flush_dcache(vid, false); - if (IS_ENABLED(CONFIG_VIDEO_COPY) && (flags & VIDSYNC_COPY)) - video_flush_dcache(vid, true); - - priv->last_sync = get_timer(0); - if (IS_ENABLED(CONFIG_VIDEO_DAMAGE)) { struct vid_bbox *damage = &priv->damage; @@ -542,6 +537,7 @@ int video_sync(struct udevice *vid, bool force) struct video_priv *priv = dev_get_uclass_priv(vid); struct video_uc_priv *uc_priv = uclass_get_priv(vid->uclass); uint flags = 0; + int ret; /* Skip sync if manual-sync mode is active */ if (uc_priv->manual_sync) @@ -558,7 +554,13 @@ int video_sync(struct udevice *vid, bool force) if (IS_ENABLED(CONFIG_VIDEO_COPY)) flags |= VIDSYNC_COPY; - return video_manual_sync(vid, flags); + ret = video_manual_sync(vid, flags); + if (ret) + return ret; + + priv->last_sync = get_timer(0); + + return 0; } void video_sync_all(void) -- 2.43.0
From: Simon Glass <sjg@chromium.org> The copy buffer is a bit confusing, so add some more documentation for it. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/video-uclass.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 37171f6b2da..f50452cc956 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -47,10 +47,32 @@ * @size and @align information and this time video_post_bind() checks that * the drivers does not overflow the allocated memory. * - * The frame buffer address is actually set (to plat->base) in - * video_post_probe(). This function also clears the frame buffer and - * allocates a suitable text console device. This can then be used to write - * text to the video device. + * The driver's probe() function is called, which should set up the hardware + * and fill in any required fields in struct video_uc_plat and + * struct video_priv. + * + * After the driver's probe() completes, video_post_probe() is called. This + * converts the framebuffer addresses (plat->base and plat->copy_base) to + * pointers (priv->fb and priv->copy_fb), clears the frame buffer, and + * allocates a suitable text-console device. The console can then be used to + * write text to the video device. + * + * Copy framebuffer (CONFIG_VIDEO_COPY): + * + * To avoid flicker, some drivers need to draw to an off-screen buffer and + * then copy to the visible framebuffer. Drivers can enable this in two ways: + * + * 1) If the framebuffer is in fixed memory (common on x86 hardware), in + * bind() leave plat->size as 0 but set up plat->copy_size. This will allocate a + * copy buffer. The driver must then set copy_base to the fixed-memory address + * and base to the allocated copy_base. See for example vesa_setup_video_priv(). + * + * 2) Otherwise, since plat->size to the required value. The off-screen + * framebuffer will be allocated. The driver must then set up copy_base in the + * probe() method. + * + * In both cases, U-Boot draws to priv->fb and video_sync() copies the + * damaged regions from priv->fb to priv->copy_fb to make them visible. */ DECLARE_GLOBAL_DATA_PTR; -- 2.43.0
From: Simon Glass <sjg@chromium.org> Document the mouse_get_event() function to explain what it does and its return values. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- include/mouse.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/mouse.h b/include/mouse.h index 7fe263b289f..8212a1e89d4 100644 --- a/include/mouse.h +++ b/include/mouse.h @@ -86,11 +86,35 @@ struct mouse_event { }; struct mouse_ops { + /** + * mouse_get_event() - Get a mouse event + * + * Gets the next available mouse event from the device. This can be a + * motion event (mouse movement) or a button event (button press or + * release). + * + * @dev: Mouse device + * @event: Returns the mouse event + * Returns: 0 if OK, -EAGAIN if no event available, -ENOSYS if not + * supported + */ int (*get_event)(struct udevice *dev, struct mouse_event *event); }; #define mouse_get_ops(dev) ((struct mouse_ops *)(dev)->driver->ops) +/** + * mouse_get_event() - Get a mouse event + * + * Gets the next available mouse event from the device. This can be a + * motion event (mouse movement) or a button event (button press or + * release). + * + * @dev: Mouse device + * @event: Returns the mouse event + * Returns: 0 if OK, -EAGAIN if no event available, -ENOSYS if not + * supported + */ int mouse_get_event(struct udevice *dev, struct mouse_event *event); /** -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a new set_ptr_visible() method to the mouse uclass to allow hiding and showing the system mouse pointer. This is useful with sandbox when rendering a custom mouse pointer, such as in expo mode. The method is optional and returns -ENOSYS if not supported by the driver. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/input/mouse-uclass.c | 10 ++++++++++ include/mouse.h | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/drivers/input/mouse-uclass.c b/drivers/input/mouse-uclass.c index ee983397ef3..efb52d3377b 100644 --- a/drivers/input/mouse-uclass.c +++ b/drivers/input/mouse-uclass.c @@ -83,6 +83,16 @@ int mouse_get_pos(struct udevice *dev, struct vid_pos *pos) return 0; } +int mouse_set_ptr_visible(struct udevice *dev, bool visible) +{ + struct mouse_ops *ops = mouse_get_ops(dev); + + if (!ops->set_ptr_visible) + return -ENOSYS; + + return ops->set_ptr_visible(dev, visible); +} + UCLASS_DRIVER(mouse) = { .id = UCLASS_MOUSE, .name = "mouse", diff --git a/include/mouse.h b/include/mouse.h index 8212a1e89d4..98f54f73d88 100644 --- a/include/mouse.h +++ b/include/mouse.h @@ -99,6 +99,18 @@ struct mouse_ops { * supported */ int (*get_event)(struct udevice *dev, struct mouse_event *event); + + /** + * set_ptr_visible() - Show or hide the system mouse pointer + * + * This is used to hide the system pointer when expo is rendering its + * own custom mouse pointer. + * + * @dev: Mouse device + * @visible: true to show the pointer, false to hide it + * Returns: 0 if OK, -ENOSYS if not supported + */ + int (*set_ptr_visible)(struct udevice *dev, bool visible); }; #define mouse_get_ops(dev) ((struct mouse_ops *)(dev)->driver->ops) @@ -135,4 +147,16 @@ int mouse_get_click(struct udevice *dev, struct vid_pos *pos); */ int mouse_get_pos(struct udevice *dev, struct vid_pos *pos); +/** + * mouse_set_ptr_visible() - Show or hide the system mouse pointer + * + * This is used to hide the system pointer when rendering a custom mouse + * pointer (e.g., in expo mode). + * + * @dev: Mouse device + * @visible: true to show the pointer, false to hide it + * Returns: 0 if OK, -ENOSYS if not supported + */ +int mouse_set_ptr_visible(struct udevice *dev, bool visible); + #endif -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a mouse_set_video() function to set up the video device associated with the mouse. This allows mouse drivers to scale coordinates to match the display resolution. The video device information is stored in mouse_uc_priv rather than being driver-specific, providing a common place for all mouse drivers to access display dimensions for coordinate scaling. Update expo_set_mouse_enable() to call mouse_set_video() to configure the mouse with the display device. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo.c | 5 +++++ drivers/input/mouse-uclass.c | 17 +++++++++++++++++ include/mouse.h | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/boot/expo.c b/boot/expo.c index 8ec301d4dcf..99b9ad4d52b 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -178,6 +178,11 @@ int expo_set_mouse_enable(struct expo *exp, bool enable) if (ret) return log_msg_ret("sme", ret); + /* Tell the mouse driver about the video device for coordinate scaling */ + ret = mouse_set_video(exp->mouse, exp->display); + if (ret) + return log_msg_ret("msv", ret); + /* Get mouse pointer image and dimensions */ exp->mouse_ptr = video_image_getptr(riscos_arrow); if (exp->mouse_ptr) { diff --git a/drivers/input/mouse-uclass.c b/drivers/input/mouse-uclass.c index efb52d3377b..4ade394d68a 100644 --- a/drivers/input/mouse-uclass.c +++ b/drivers/input/mouse-uclass.c @@ -7,6 +7,7 @@ #include <dm.h> #include <errno.h> #include <mouse.h> +#include <video.h> int mouse_get_event(struct udevice *dev, struct mouse_event *evt) { @@ -93,6 +94,22 @@ int mouse_set_ptr_visible(struct udevice *dev, bool visible) return ops->set_ptr_visible(dev, visible); } +int mouse_set_video(struct udevice *dev, struct udevice *video_dev) +{ + struct mouse_uc_priv *uc_priv = dev_get_uclass_priv(dev); + + uc_priv->video_dev = video_dev; + if (video_dev) { + uc_priv->video_width = video_get_xsize(video_dev); + uc_priv->video_height = video_get_ysize(video_dev); + } else { + uc_priv->video_width = 0; + uc_priv->video_height = 0; + } + + return 0; +} + UCLASS_DRIVER(mouse) = { .id = UCLASS_MOUSE, .name = "mouse", diff --git a/include/mouse.h b/include/mouse.h index 98f54f73d88..560c7d14587 100644 --- a/include/mouse.h +++ b/include/mouse.h @@ -38,11 +38,17 @@ enum mouse_press_state_t { * @left_button_state: Current state of left button (BUTTON_PRESSED/BUTTON_RELEASED) * @click_pos: Position where the click occurred * @last_pos: Last position received from mouse + * @video_dev: Video device for coordinate scaling + * @video_width: Width of video display + * @video_height: Height of video display */ struct mouse_uc_priv { enum mouse_press_state_t left_button_state; struct vid_pos click_pos; struct vid_pos last_pos; + struct udevice *video_dev; + int video_width; + int video_height; }; /** @@ -159,4 +165,16 @@ int mouse_get_pos(struct udevice *dev, struct vid_pos *pos); */ int mouse_set_ptr_visible(struct udevice *dev, bool visible); +/** + * mouse_set_video() - Set the video device for coordinate scaling + * + * Sets up the video device in the mouse uclass private data so mouse drivers + * can scale coordinates to match the display resolution. + * + * @dev: Mouse device + * @video_dev: Video device + * Returns: 0 if OK, -ve on error + */ +int mouse_set_video(struct udevice *dev, struct udevice *video_dev); + #endif -- 2.43.0
From: Simon Glass <sjg@chromium.org> Implement this new method using the SDL layer. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/cpu/sdl.c | 5 +++++ arch/sandbox/include/asm/sdl.h | 11 +++++++++++ drivers/input/sandbox_mouse.c | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/arch/sandbox/cpu/sdl.c b/arch/sandbox/cpu/sdl.c index 65d55b0085d..c3745fa660e 100644 --- a/arch/sandbox/cpu/sdl.c +++ b/arch/sandbox/cpu/sdl.c @@ -646,3 +646,8 @@ int sandbox_sdl_sound_stop(void) return 0; } + +void sandbox_sdl_set_cursor_visible(bool visible) +{ + SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); +} diff --git a/arch/sandbox/include/asm/sdl.h b/arch/sandbox/include/asm/sdl.h index b97773c8a4f..a80db51ad19 100644 --- a/arch/sandbox/include/asm/sdl.h +++ b/arch/sandbox/include/asm/sdl.h @@ -119,6 +119,13 @@ int sandbox_sdl_set_bpp(struct udevice *dev, enum video_log2_bpp l2bpp); */ int sandbox_sdl_get_mouse_event(struct mouse_event *evt); +/** + * sandbox_sdl_set_cursor_visible() - Show or hide the SDL cursor + * + * @visible: true to show the cursor, false to hide it + */ +void sandbox_sdl_set_cursor_visible(bool visible); + #else static inline int sandbox_sdl_init_display(int width, int height, int log2_bpp, bool double_size) @@ -178,6 +185,10 @@ static inline int sandbox_sdl_get_mouse_event(struct mouse_event *evt) return -ENODEV; } +static inline void sandbox_sdl_set_cursor_visible(bool visible) +{ +} + #endif #endif diff --git a/drivers/input/sandbox_mouse.c b/drivers/input/sandbox_mouse.c index ba271242b1a..add7401c4ec 100644 --- a/drivers/input/sandbox_mouse.c +++ b/drivers/input/sandbox_mouse.c @@ -36,8 +36,16 @@ static int mouse_sandbox_get_event(struct udevice *dev, return ret; } +static int mouse_sandbox_set_ptr_visible(struct udevice *dev, bool visible) +{ + sandbox_sdl_set_cursor_visible(visible); + + return 0; +} + const struct mouse_ops mouse_sandbox_ops = { .get_event = mouse_sandbox_get_event, + .set_ptr_visible = mouse_sandbox_set_ptr_visible, }; static const struct udevice_id mouse_sandbox_ids[] = { -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a test for the mouse set_ptr_visible() method. This uses a back-door function to read the visibility state from the sandbox mouse driver. Also add documentation for struct sandbox_mouse_priv. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/include/asm/test.h | 8 ++++++++ drivers/input/sandbox_mouse.c | 25 +++++++++++++++++++++++++ test/dm/mouse.c | 22 ++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h index 499db42804c..ba8f269d86f 100644 --- a/arch/sandbox/include/asm/test.h +++ b/arch/sandbox/include/asm/test.h @@ -378,4 +378,12 @@ void sandbox_mouse_set_test_mode(struct udevice *dev, bool test_mode); */ void sandbox_mouse_inject(struct udevice *dev, struct mouse_event *event); +/** + * sandbox_mouse_get_ptr_visible() - Get pointer visibility state + * + * @dev: Mouse device + * Return: true if pointer is visible, false if hidden + */ +bool sandbox_mouse_get_ptr_visible(struct udevice *dev); + #endif diff --git a/drivers/input/sandbox_mouse.c b/drivers/input/sandbox_mouse.c index add7401c4ec..faebb09ba3c 100644 --- a/drivers/input/sandbox_mouse.c +++ b/drivers/input/sandbox_mouse.c @@ -8,10 +8,19 @@ #include <mouse.h> #include <asm/sdl.h> +/** + * struct sandbox_mouse_priv - Private data for sandbox mouse driver + * + * @test_mode: true to use test mode (inject events), false to use SDL + * @test_event: Event to return when in test mode + * @test_event_pending: true if test_event is pending + * @ptr_visible: Current visibility state of mouse pointer + */ struct sandbox_mouse_priv { bool test_mode; struct mouse_event test_event; bool test_event_pending; + bool ptr_visible; }; static int mouse_sandbox_get_event(struct udevice *dev, @@ -38,6 +47,9 @@ static int mouse_sandbox_get_event(struct udevice *dev, static int mouse_sandbox_set_ptr_visible(struct udevice *dev, bool visible) { + struct sandbox_mouse_priv *priv = dev_get_priv(dev); + + priv->ptr_visible = visible; sandbox_sdl_set_cursor_visible(visible); return 0; @@ -83,6 +95,19 @@ void sandbox_mouse_inject(struct udevice *dev, struct mouse_event *event) } } +/** + * sandbox_mouse_get_ptr_visible() - Get pointer visibility state + * + * @dev: Mouse device + * Return: true if pointer is visible, false if hidden + */ +bool sandbox_mouse_get_ptr_visible(struct udevice *dev) +{ + struct sandbox_mouse_priv *priv = dev_get_priv(dev); + + return priv->ptr_visible; +} + U_BOOT_DRIVER(mouse_sandbox) = { .name = "mouse_sandbox", .id = UCLASS_MOUSE, diff --git a/test/dm/mouse.c b/test/dm/mouse.c index 3efff4a0d7d..c22cd026cb2 100644 --- a/test/dm/mouse.c +++ b/test/dm/mouse.c @@ -214,3 +214,25 @@ static int dm_test_mouse_right_button(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_mouse_right_button, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +static int dm_test_mouse_ptr_visible(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_first_device_err(UCLASS_MOUSE, &dev)); + + /* test hiding the pointer */ + ut_assertok(mouse_set_ptr_visible(dev, false)); + ut_asserteq(false, sandbox_mouse_get_ptr_visible(dev)); + + /* test showing the pointer */ + ut_assertok(mouse_set_ptr_visible(dev, true)); + ut_asserteq(true, sandbox_mouse_get_ptr_visible(dev)); + + /* test hiding again */ + ut_assertok(mouse_set_ptr_visible(dev, false)); + ut_asserteq(false, sandbox_mouse_get_ptr_visible(dev)); + + return 0; +} +DM_TEST(dm_test_mouse_ptr_visible, UTF_SCAN_PDATA | UTF_SCAN_FDT); -- 2.43.0
From: Simon Glass <sjg@chromium.org> When entering expo mode, hide the system mouse pointer so that only the custom expo pointer is visible. Restore it when exiting expo mode. This uses the new set_ptr_visible() method if a mouse is enabled. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boot/expo.c b/boot/expo.c index 99b9ad4d52b..afb09aaf5b5 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -545,11 +545,15 @@ void expo_req_size(struct expo *exp, int width, int height) void expo_enter_mode(struct expo *exp) { video_manual_sync(exp->display, true); + if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) + mouse_set_ptr_visible(exp->mouse, false); } void expo_exit_mode(struct expo *exp) { video_manual_sync(exp->display, false); + if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) + mouse_set_ptr_visible(exp->mouse, true); } void expo_damage_reset(struct expo *exp) -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a test mode for expo which will display useful debugging information. Put this feature behind a CONFIG_EXPO_TEST option to avoid code-size growth. Create a separate C file and a header. Use static inlines to avoid lots of CONFIG checking in expo.c Enable this feature for sandbox and the EFI app for now. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/Kconfig | 10 +++++++++ boot/Makefile | 1 + boot/expo.c | 11 ++++++++++ boot/expo_test.c | 25 +++++++++++++++++++++ include/expo_test.h | 53 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 boot/expo_test.c create mode 100644 include/expo_test.h diff --git a/boot/Kconfig b/boot/Kconfig index 933832b4dbf..fb34a10106b 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -982,6 +982,16 @@ config EXPO The expo can be presented in graphics form using a vidconsole, or in text form on a serial console. +config EXPO_TEST + bool "Enable test mode for expo" + depends on EXPO + default y if SANDBOX || EFI_APP + help + Enable test mode for expo. When enabled, expo displays a frame count + in the top-right corner of the display when the 'expotest' environment + variable is set to 1. This is useful for debugging and performance + analysis. + config BOOTMETH_SANDBOX def_bool y depends on SANDBOX diff --git a/boot/Makefile b/boot/Makefile index 3ab1fbb11c0..bb1888f1656 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_$(PHASE_)LOAD_FIT) += common_fit.o obj-$(CONFIG_$(PHASE_)EXPO) += expo.o scene.o expo_build.o obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o scene_textedit.o +obj-$(CONFIG_$(PHASE_)EXPO_TEST) += expo_test.o ifdef CONFIG_COREBOOT_SYSINFO obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo_build_cb.o endif diff --git a/boot/expo.c b/boot/expo.c index afb09aaf5b5..e7c4ab8d7db 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -10,6 +10,7 @@ #include <dm.h> #include <expo.h> +#include <expo_test.h> #include <log.h> #include <malloc.h> #include <mapmem.h> @@ -23,6 +24,7 @@ int expo_new(const char *name, void *priv, struct expo **expp) { struct expo *exp; + int ret; exp = calloc(1, sizeof(struct expo)); if (!exp) @@ -32,6 +34,12 @@ int expo_new(const char *name, void *priv, struct expo **expp) free(exp); return log_msg_ret("name", -ENOMEM); } + ret = expo_test_init(exp); + if (ret) { + free(exp->name); + free(exp); + return log_msg_ret("tst", ret); + } exp->priv = priv; INIT_LIST_HEAD(&exp->scene_head); INIT_LIST_HEAD(&exp->str_head); @@ -53,6 +61,7 @@ void expo_destroy(struct expo *exp) struct scene *scn, *next; struct expo_string *estr, *enext; + expo_test_uninit(exp); list_for_each_entry_safe(scn, next, &exp->scene_head, sibling) scene_destroy(scn); @@ -314,6 +323,8 @@ static int expo_render_(struct expo *exp, bool dirty_only) u32 colour; int ret; + expo_test_update(exp); + back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE; colour = video_index_to_colour(vid_priv, back); ret = video_fill(dev, colour); diff --git a/boot/expo_test.c b/boot/expo_test.c new file mode 100644 index 00000000000..ecef8decc6e --- /dev/null +++ b/boot/expo_test.c @@ -0,0 +1,25 @@ +// 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 <expo.h> +#include <expo_test.h> + +int expo_test_init(struct expo *exp) +{ + return 0; +} + +void expo_test_uninit(struct expo *exp) +{ +} + +void expo_test_update(struct expo *exp) +{ +} diff --git a/include/expo_test.h b/include/expo_test.h new file mode 100644 index 00000000000..e1918ddeff2 --- /dev/null +++ b/include/expo_test.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright Canonical Ltd + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __EXPO_TEST_H +#define __EXPO_TEST_H + +struct expo; + +#if CONFIG_IS_ENABLED(EXPO_TEST) + +/** + * expo_test_init() - Initialize test mode for an expo + * + * @exp: Expo to initialize test mode for + * Return: 0 if OK, -ve on error + */ +int expo_test_init(struct expo *exp); + +/** + * expo_test_uninit() - Uninitialize test mode for an expo + * + * @exp: Expo to uninitialize test mode for + */ +void expo_test_uninit(struct expo *exp); + +/** + * expo_test_update() - Update test mode counters + * + * @exp: Expo to update test mode for + */ +void expo_test_update(struct expo *exp); + +#else + +static inline int expo_test_init(struct expo *exp) +{ + return 0; +} + +static inline void expo_test_uninit(struct expo *exp) +{ +} + +static inline void expo_test_update(struct expo *exp) +{ +} + +#endif /* EXPO_TEST */ + +#endif /* __EXPO_TEST_H */ -- 2.43.0
From: Simon Glass <sjg@chromium.org> Keep track of the frame count, i.e. the number of times that expo_render() has been called since expo_enter_mode() was invoked. Allow test mode to be enabled (if compiled in) by setting the 'expotest' environment variable to 1. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo_test.c | 21 +++++++++++++++++++++ include/expo.h | 4 ++++ include/expo_test.h | 11 +++++++++++ 3 files changed, 36 insertions(+) diff --git a/boot/expo_test.c b/boot/expo_test.c index ecef8decc6e..269052a61cb 100644 --- a/boot/expo_test.c +++ b/boot/expo_test.c @@ -8,18 +8,39 @@ #define LOG_CATEGORY LOGC_EXPO +#include <env.h> +#include <errno.h> #include <expo.h> #include <expo_test.h> +#include <log.h> +#include <malloc.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); + + test->enabled = env_get_yesno("expotest") == 1; + exp->test = test; + return 0; } void expo_test_uninit(struct expo *exp) { + free(exp->test); + exp->test = NULL; } void expo_test_update(struct expo *exp) { + struct expo_test_mode *test = exp->test; + + if (!test) + return; + + test->render_count++; } diff --git a/include/expo.h b/include/expo.h index 8ad7415b5a4..e9e71f4fe36 100644 --- a/include/expo.h +++ b/include/expo.h @@ -90,6 +90,8 @@ struct expo_action { }; }; +struct expo_test_mode; + /** * struct expo_theme - theme for the expo * @@ -139,6 +141,7 @@ struct expo_theme { * @priv: Private data for the controller * @done: Indicates that a cedit session is complete and the user has quit * @save: Indicates that cedit data should be saved, rather than discarded + * @test: Pointer to test mode information, NULL if not allocated * @theme: Information about fonts styles, etc. * @scene_head: List of scenes * @str_head: list of strings @@ -165,6 +168,7 @@ struct expo { void *priv; bool done; bool save; + struct expo_test_mode *test; struct expo_theme theme; struct list_head scene_head; struct list_head str_head; diff --git a/include/expo_test.h b/include/expo_test.h index e1918ddeff2..ee4ae4611f7 100644 --- a/include/expo_test.h +++ b/include/expo_test.h @@ -9,6 +9,17 @@ struct expo; +/** + * struct expo_test_mode - Test mode information for expo + * + * @enabled: true if test mode is enabled + * @render_count: Number of calls to expo_render() since expo_enter_mode() + */ +struct expo_test_mode { + bool enabled; + int render_count; +}; + #if CONFIG_IS_ENABLED(EXPO_TEST) /** -- 2.43.0
From: Simon Glass <sjg@chromium.org> When expo-test is enabled, show the frame count in the top right of the display. This allows an easy visual check that expo is working correctly, and provides an indication of performance. Add a test for this also. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo.c | 7 +++++ boot/expo_test.c | 47 +++++++++++++++++++++++++++++--- include/expo_test.h | 27 +++++++++++++++++++ test/boot/expo.c | 66 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 4 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index e7c4ab8d7db..2a75eed2c39 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -346,6 +346,11 @@ static int expo_render_(struct expo *exp, bool dirty_only) if (ret) return log_msg_ret("mou", ret); + /* Render test-mode info if enabled */ + ret = expo_test_render(exp); + if (ret) + return log_msg_ret("tst", ret); + video_sync(dev, true); return scn ? 0 : -ECHILD; @@ -558,6 +563,8 @@ void expo_enter_mode(struct expo *exp) video_manual_sync(exp->display, true); if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) mouse_set_ptr_visible(exp->mouse, false); + + expo_test_checkenv(exp); } void expo_exit_mode(struct expo *exp) diff --git a/boot/expo_test.c b/boot/expo_test.c index 269052a61cb..0ac2d0551a1 100644 --- a/boot/expo_test.c +++ b/boot/expo_test.c @@ -8,12 +8,15 @@ #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 <video.h> +#include <video_console.h> int expo_test_init(struct expo *exp) { @@ -23,8 +26,8 @@ int expo_test_init(struct expo *exp) if (!test) return log_msg_ret("test", -ENOMEM); - test->enabled = env_get_yesno("expotest") == 1; exp->test = test; + expo_test_checkenv(exp); return 0; } @@ -35,12 +38,48 @@ void expo_test_uninit(struct expo *exp) exp->test = NULL; } -void expo_test_update(struct expo *exp) +void expo_test_checkenv(struct expo *exp) { struct expo_test_mode *test = exp->test; - if (!test) - return; + test->enabled = env_get_yesno("expotest") == 1; + test->render_count = 0; +} + +void expo_test_update(struct expo *exp) +{ + struct expo_test_mode *test = exp->test; test->render_count++; } + +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]; + int x, y; + int ret; + + if (!test->enabled) + return 0; + + /* 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); + + /* 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); + + return 0; +} diff --git a/include/expo_test.h b/include/expo_test.h index ee4ae4611f7..88cec4a2a96 100644 --- a/include/expo_test.h +++ b/include/expo_test.h @@ -37,6 +37,16 @@ int expo_test_init(struct expo *exp); */ void expo_test_uninit(struct expo *exp); +/** + * expo_test_checkenv() - Check environment and reset test mode + * + * @exp: Expo to update test mode for + * + * Checks the expotest environment variable and updates the enabled flag + * accordingly. Also resets the render count to 0. + */ +void expo_test_checkenv(struct expo *exp); + /** * expo_test_update() - Update test mode counters * @@ -44,6 +54,14 @@ void expo_test_uninit(struct expo *exp); */ void expo_test_update(struct expo *exp); +/** + * expo_test_render() - Render test mode information + * + * @exp: Expo to render test info for + * Return: 0 if OK, -ve on error + */ +int expo_test_render(struct expo *exp); + #else static inline int expo_test_init(struct expo *exp) @@ -55,10 +73,19 @@ static inline void expo_test_uninit(struct expo *exp) { } +static inline void expo_test_checkenv(struct expo *exp) +{ +} + static inline void expo_test_update(struct expo *exp) { } +static inline int expo_test_render(struct expo *exp) +{ + return 0; +} + #endif /* EXPO_TEST */ #endif /* __EXPO_TEST_H */ diff --git a/test/boot/expo.c b/test/boot/expo.c index 7723a301c9f..a1aa543ab71 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -7,6 +7,7 @@ #include <command.h> #include <dm.h> #include <expo.h> +#include <expo_test.h> #include <menu.h> #include <video.h> #include <linux/input.h> @@ -1097,3 +1098,68 @@ static int expo_mouse_click(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(expo_mouse_click, UTF_DM | UTF_SCAN_FDT); + +static int expo_test_mode(struct unit_test_state *uts) +{ + struct scene_obj_menu *menu; + struct abuf buf, logo_copy; + struct udevice *dev; + struct scene *scn; + struct expo *exp; + + ut_assertok(create_test_expo(uts, &exp, &scn, &menu, &buf, &logo_copy)); + dev = exp->display; + + /* Check test mode is initially off */ + ut_asserteq(false, exp->test->enabled); + + /* Entering expo mode without expotest env var keeps it off */ + expo_enter_mode(exp); + ut_asserteq(false, exp->test->enabled); + expo_exit_mode(exp); + + /* Enable test mode */ + ut_assertok(env_set("expotest", "1")); + expo_enter_mode(exp); + ut_asserteq(true, exp->test->enabled); + + /* Check initial render count */ + ut_asserteq(0, exp->test->render_count); + + /* Render and check count increments */ + ut_assertok(expo_set_scene_id(exp, scn->id)); + ut_assertok(scene_arrange(scn)); + ut_assertok(expo_render(exp)); + ut_asserteq(1, exp->test->render_count); + + ut_assertok(expo_render(exp)); + ut_asserteq(2, exp->test->render_count); + + /* Test that expo_enter_mode() resets the counter */ + expo_exit_mode(exp); + expo_enter_mode(exp); + ut_asserteq(0, exp->test->render_count); + ut_assertok(expo_render(exp)); + ut_asserteq(1, exp->test->render_count); + expo_exit_mode(exp); + + /* Disable test mode */ + ut_assertok(env_set("expotest", "0")); + expo_enter_mode(exp); + ut_asserteq(false, exp->test->enabled); + expo_exit_mode(exp); + + /* Check test mode is off when env var is unset */ + ut_assertok(env_set("expotest", NULL)); + expo_enter_mode(exp); + ut_asserteq(false, exp->test->enabled); + expo_exit_mode(exp); + + ut_assertok(env_set("expotest", NULL)); + abuf_uninit(&buf); + abuf_uninit(&logo_copy); + expo_destroy(exp); + + return 0; +} +BOOTSTD_TEST(expo_test_mode, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
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
From: Simon Glass <sjg@chromium.org> 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> --- boot/expo.c | 11 ++++++- boot/expo_test.c | 74 +++++++++++++++++++++++++++++++++++++++++++- doc/develop/expo.rst | 28 ++++++++++++++--- include/expo_test.h | 62 +++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 6 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 2a75eed2c39..4f2c6d928a3 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -323,6 +323,7 @@ static int expo_render_(struct expo *exp, bool dirty_only) u32 colour; int ret; + expo_test_mark(exp); expo_test_update(exp); back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE; @@ -352,6 +353,7 @@ static int expo_render_(struct expo *exp, bool dirty_only) return log_msg_ret("tst", ret); video_sync(dev, true); + expo_test_sync(exp); return scn ? 0 : -ECHILD; } @@ -528,6 +530,8 @@ int expo_poll(struct expo *exp, struct expo_action *act) { int key, ret = -EAGAIN; + expo_test_mark(exp); + /* update mouse position if mouse is enabled */ update_mouse_position(exp); @@ -541,11 +545,16 @@ int expo_poll(struct expo *exp, struct expo_action *act) if (!ret) ret = expo_send_click(exp, pos.x, pos.y); } - if (ret) + if (ret) { + expo_test_poll(exp); return log_msg_ret("epk", ret); + } /* get the action (either a key or a click) */ ret = expo_action_get(exp, act); + + expo_test_poll(exp); + if (ret) return log_msg_ret("eag", ret); diff --git a/boot/expo_test.c b/boot/expo_test.c index 726027aabb6..3ddeb86fb2c 100644 --- a/boot/expo_test.c +++ b/boot/expo_test.c @@ -49,6 +49,13 @@ void expo_test_checkenv(struct expo *exp) 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; @@ -56,6 +63,20 @@ void expo_test_update(struct expo *exp) 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; @@ -108,6 +129,10 @@ int expo_test_render(struct expo *exp) 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) @@ -116,13 +141,36 @@ 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 */ + /* 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); } @@ -141,5 +189,29 @@ int expo_test_render(struct expo *exp) 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; } diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 85ccbe7fd63..9ce00e621e5 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -578,10 +578,10 @@ 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 +variable `expotest` to 1. When enabled, expo displays performance metrics in the top-right corner of the display. This is useful for debugging and performance analysis. @@ -590,8 +590,28 @@ 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. +Test mode displays the following metrics: + +Frame count + Shows the total number of frames rendered. This is the number of times + `expo_render()` has been called since `expo_enter_mode()` was invoked. + The counter resets each time expo mode is entered. + +FPS (frames per second) + Shows the rendering rate averaged over the past 5 seconds. This provides + a stable indication of rendering performance. + +Timing information + Shows average timings (in millisecond) for the following operations, + measured over the past second: + + - Render: Time taken to render the scene + - Sync: Time taken to sync the framebuffer to the display + - Poll: Time taken to poll for keyboard/mouse input + +These metrics help identify performance bottlenecks and verify that expo is +operating efficiently. The timing information is particularly useful when +optimizing display drivers or debugging slow rendering issues. Some ideas for future work: diff --git a/include/expo_test.h b/include/expo_test.h index 9d557e29c87..6363e70a3e5 100644 --- a/include/expo_test.h +++ b/include/expo_test.h @@ -23,6 +23,17 @@ struct expo; * @fps_index: Current index in the FPS tracking arrays * @fps_last: Last calculated FPS value * @last_update: Time of last FPS update (milliseconds) + * @base_time_us: Base time in microseconds for delta calculations + * @render_delta_us: Time between update and render in microseconds + * @sync_delta_us: Time taken by video_manual_sync() in microseconds + * @poll_delta_us: Time taken by expo_poll() in microseconds + * @render_total_us: Cumulative render time in current second (us) + * @sync_total_us: Cumulative sync time in current second (us) + * @poll_total_us: Cumulative poll time in current second (us) + * @frame_count_last_sec: Number of frames in current measurement second + * @render_avg_us: Average render time over last second (microseconds) + * @sync_avg_us: Average sync time over last second (microseconds) + * @poll_avg_us: Average poll time over last second (microseconds) */ struct expo_test_mode { bool enabled; @@ -33,6 +44,17 @@ struct expo_test_mode { int fps_index; int fps_last; ulong last_update; + ulong base_time_us; + ulong render_delta_us; + ulong sync_delta_us; + ulong poll_delta_us; + ulong render_total_us; + ulong sync_total_us; + ulong poll_total_us; + int frame_count_last_sec; + ulong render_avg_us; + ulong sync_avg_us; + ulong poll_avg_us; }; #if CONFIG_IS_ENABLED(EXPO_TEST) @@ -62,6 +84,16 @@ void expo_test_uninit(struct expo *exp); */ void expo_test_checkenv(struct expo *exp); +/** + * expo_test_mark() - Mark the current time for delta calculations + * + * @exp: Expo to update test mode for + * + * Records the current time in microseconds as the base time for subsequent + * delta calculations + */ +void expo_test_mark(struct expo *exp); + /** * expo_test_update() - Update test mode counters * @@ -69,6 +101,24 @@ void expo_test_checkenv(struct expo *exp); */ void expo_test_update(struct expo *exp); +/** + * expo_test_poll() - Calculate poll delta time + * + * @exp: Expo to update test mode for + * + * Calculates the time taken by expo_poll() based on the base time + */ +void expo_test_poll(struct expo *exp); + +/** + * expo_test_sync() - Calculate sync delta time + * + * @exp: Expo to update test mode for + * + * Calculates the time taken by video_manual_sync() based on the base time + */ +void expo_test_sync(struct expo *exp); + /** * expo_test_render() - Render test mode information * @@ -100,10 +150,22 @@ static inline void expo_test_checkenv(struct expo *exp) { } +static inline void expo_test_mark(struct expo *exp) +{ +} + static inline void expo_test_update(struct expo *exp) { } +static inline void expo_test_poll(struct expo *exp) +{ +} + +static inline void expo_test_sync(struct expo *exp) +{ +} + static inline int expo_test_render(struct expo *exp) { return 0; -- 2.43.0
From: Simon Glass <sjg@chromium.org> Make use of the manual-sync mode provided by the video subsystem. This removes uncertainty about whether the display will actually be updated. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/expo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 4f2c6d928a3..ebe31059e87 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -352,7 +352,7 @@ static int expo_render_(struct expo *exp, bool dirty_only) if (ret) return log_msg_ret("tst", ret); - video_sync(dev, true); + video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH); expo_test_sync(exp); return scn ? 0 : -ECHILD; @@ -569,7 +569,7 @@ void expo_req_size(struct expo *exp, int width, int height) void expo_enter_mode(struct expo *exp) { - video_manual_sync(exp->display, true); + video_set_manual_sync(true); if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) mouse_set_ptr_visible(exp->mouse, false); @@ -578,7 +578,7 @@ void expo_enter_mode(struct expo *exp) void expo_exit_mode(struct expo *exp) { - video_manual_sync(exp->display, false); + video_set_manual_sync(false); if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) mouse_set_ptr_visible(exp->mouse, true); } -- 2.43.0
participants (1)
- 
                
Simon Glass