Skip to content

Commit 910c8d0

Browse files
committed
MENU/XMB: defer xmb_populate_dynamic_icons until horizontal tab animation settles
On rapid left/right traversal across a row of playlist tabs, each intermediate tab would get a full xmb_populate_dynamic_icons pass — xmb_unload_icon_thumbnail_textures (loop over every node resetting icon textures, cancel in-flight requests, clear pending_icons) followed by xmb_refresh_visible_icon_paths (loop over visible entries writing path_data) — only to be clobbered by the next tab's pass ~16 ms later. None of the intermediate work ever renders anything the user sees, because the tab-switch animation is still mid-flight when the next step arrives. Defer the work via a new `pending_dynamic_icons_repopulate` flag: xmb_populate_entries sets it instead of calling the function directly; xmb_render fires it once per frame, but only when the horizontal tab animation has settled (categories_x_pos has reached its target — a reliable predicate now that tweens on that float are tagged and kill-before-pushed, from the prior commit). Effects on rapid tab traversal: - Each intermediate populate_entries just re-sets the flag (idempotent). - xmb_render skips firing while categories_x_pos is still tweening. - Only once animation settles on the final tab does the unload + refresh actually run, with is_playlist / menu_icon_thumbnails gates re-checked at fire time so the *current* landing tab's state decides what happens. - If the user navigated away from a playlist during the animation (e.g. to settings), the else-if unload branch from populate_entries runs instead. Cleared in two places to avoid stale-fire hazards: - xmb_context_reset_internal does a synchronous populate and clears the flag so xmb_render doesn't redundantly repeat the work a few frames later when the animation settles. - xmb_context_destroy clears the flag because the matching context_reset_internal will repopulate synchronously; firing between destroy and reset would walk the selection list with already-reset thumbnail state only to have it wiped again. No effect on the common case of simple cursor navigation within a single playlist: categories_x_pos is already at target when populate_entries fires, so the deferred work runs on the next render frame — essentially immediate.
1 parent 04099df commit 910c8d0

1 file changed

Lines changed: 58 additions & 2 deletions

File tree

menu/drivers/xmb.c

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,13 @@ typedef struct xmb_handle
468468
bool show_fullscreen_thumbnails;
469469
bool want_fullscreen_thumbnails;
470470
bool skip_thumbnail_reset;
471+
/* Set by xmb_populate_entries when a list is pushed and gated deferred
472+
* dynamic-icon work is needed. xmb_render fires the actual work once
473+
* the horizontal tab animation has settled (categories_x_pos has
474+
* reached its target). Lets rapid left/right traversal across
475+
* playlists skip the per-tab unload+path_data churn for intermediate
476+
* tabs the user never lands on. */
477+
bool pending_dynamic_icons_repopulate;
471478
bool show_thumbnails;
472479
bool show_mouse;
473480
bool show_screensaver;
@@ -3480,8 +3487,12 @@ static void xmb_populate_entries(void *data,
34803487

34813488
if (xmb->is_playlist)
34823489
{
3483-
if (settings->uints.menu_icon_thumbnails)
3484-
xmb_populate_dynamic_icons(xmb);
3490+
/* Defer the dynamic-icon unload + path_data refresh until the
3491+
* horizontal tab animation has settled. xmb_render re-checks the
3492+
* is_playlist / menu_icon_thumbnails gates at fire time so that
3493+
* rapidly traversing a row of playlist tabs only does the work
3494+
* once, on the tab the user actually stops on. */
3495+
xmb->pending_dynamic_icons_repopulate = true;
34853496
}
34863497
else if (xmb->thumbnails.pending_icons != XMB_PENDING_THUMBNAIL_NONE)
34873498
xmb_unload_icon_thumbnail_textures(xmb);
@@ -6957,7 +6968,14 @@ static void xmb_context_reset_internal(xmb_handle_t *xmb,
69576968
xmb_update_thumbnail_image(xmb);
69586969

69596970
if (xmb->is_playlist)
6971+
{
6972+
/* Synchronous populate during context reset — ensures textures
6973+
* and path_data are consistent before the next render. Clear
6974+
* any deferred repopulate flag so xmb_render does not
6975+
* redundantly repeat this work after the animation settles. */
6976+
xmb->pending_dynamic_icons_repopulate = false;
69606977
xmb_populate_dynamic_icons(xmb);
6978+
}
69616979
}
69626980

69636981
/* Have to reset this, otherwise savestate
@@ -7018,6 +7036,37 @@ static void xmb_render(void *data,
70187036
settings->uints.menu_xmb_theme);
70197037
}
70207038

7039+
/* Fire deferred dynamic-icon repopulate once the horizontal tab
7040+
* animation has settled. Set by xmb_populate_entries; held pending
7041+
* while categories_x_pos is still tweening toward its target.
7042+
* Re-checks is_playlist and menu_icon_thumbnails at fire time: the
7043+
* list that was pushed when the flag was set may no longer be the
7044+
* list the user is looking at (rapid tab traversal, or navigation
7045+
* during the animation), and the gate uses the *current* state.
7046+
* If the final list is not a playlist, the pending-icons unload
7047+
* branch from populate_entries runs here instead. */
7048+
if (xmb->pending_dynamic_icons_repopulate)
7049+
{
7050+
float cat_target = xmb->icon_spacing_horizontal
7051+
* -(float)xmb->categories_selection_ptr;
7052+
/* Half-pixel tolerance. categories_x_pos is an exact match at
7053+
* animation completion (gfx_animation_update assigns target_value
7054+
* on the final tick), but a tolerance keeps the predicate robust
7055+
* against any future easing / sub-pixel drift. */
7056+
if (fabsf(xmb->categories_x_pos - cat_target) < 0.5f)
7057+
{
7058+
xmb->pending_dynamic_icons_repopulate = false;
7059+
7060+
if (xmb->is_playlist)
7061+
{
7062+
if (settings->uints.menu_icon_thumbnails)
7063+
xmb_populate_dynamic_icons(xmb);
7064+
}
7065+
else if (xmb->thumbnails.pending_icons != XMB_PENDING_THUMBNAIL_NONE)
7066+
xmb_unload_icon_thumbnail_textures(xmb);
7067+
}
7068+
}
7069+
70217070
/* Retry current-menu-icon resolution if a previous xmb_set_title()
70227071
* (typically the one invoked from xmb_context_reset_internal() after
70237072
* a fullscreen toggle) could not resolve the icon because an
@@ -9832,6 +9881,13 @@ static void xmb_context_destroy(void *data)
98329881
xmb_unload_thumbnail_textures(xmb);
98339882
xmb_unload_icon_thumbnail_textures(xmb);
98349883

9884+
/* Any deferred dynamic-icon repopulate is about to be re-done by the
9885+
* matching xmb_context_reset_internal (synchronous populate there).
9886+
* Clear the flag so xmb_render doesn't fire between destroy and
9887+
* reset, which would walk the selection list with fresh-reset
9888+
* thumbnail state only to have it all wiped again by the reset. */
9889+
xmb->pending_dynamic_icons_repopulate = false;
9890+
98359891
xmb_context_destroy_horizontal_list(xmb);
98369892
xmb_context_bg_destroy(xmb);
98379893

0 commit comments

Comments
 (0)