Commit f340815
committed
MENU/XMB: async-only icon thumbnail loading, lazy path resolution, input-repeat populate defer
Three related changes addressing animation smoothness and fps issues
around XMB playlist icon thumbnails, plus replacing a reverted-on-master
defer commit with a correct mechanism.
**Drop synchronous icon loads in favour of async stream-only**
xmb_render's icon dispatcher previously sync-loaded textures via
xmb_load_dynamic_icon (gfx_display_reset_icon_texture →
image_texture_load + video_driver_texture_load), with
gfx_thumbnail_request_stream as a fallback only when the sync path
failed. Earlier iterations of this code added and removed a
2-load-per-frame cap on the sync path; both extremes had problems:
- Capped sync (2/frame): 10–20 visible entries take 5–10 frames
to drip-load, each frame still doing enough work to dip below
refresh rate. Result: a quarter-second of visible stutter in
what is supposed to be steady-state 60 Hz list display.
- Uncapped sync (one-frame burst): the whole load lands in a
single frame which blocks the main thread long enough that the
runloop misses several gfx_animation_update ticks. The next
tick advances delta_time by the full elapsed wall-clock and
tween easing functions evaluate near or past their duration,
snapping the in-flight enter-playlist or tab-switch animation
straight to its end state.
Async stream requests don't have either problem. Worker threads
do the decode + upload in parallel with rendering, results land
via task callback over the next several frames. The main thread
stays responsive throughout, animations tick smoothly, and total
time-to-thumbnail is roughly the same as the uncapped sync burst
(typically ~50–200 ms) but distributed across frames instead of
concentrated in one.
Always dispatch async, never sync. Path resolution stays inline
(it's a few stat syscalls per unresolved entry, cheap enough not
to block a frame). xmb_load_dynamic_icon is no longer used
anywhere and is removed.
**Lazy path resolution**
Previously xmb_populate_dynamic_icons and xmb_selection_pointer_changed
both called xmb_refresh_visible_icon_paths, which eagerly walked
the visible range (~20 entries) and ran gfx_thumbnail_update_path
per entry. That function does path_is_valid checks — one stat
syscall per entry, or five per entry with playlist_allow_non_png
enabled across the .png/.jpg/.jpeg/.bmp/.tga fallback chain. On a
populate frame, 20–100 stat syscalls executed before any work
was dispatched. Worse: selection_pointer_changed fires on every
vertical scroll step, so held scrolling paid the full syscall
storm repeatedly — because gfx_thumbnail_set_icon_playlist
unconditionally clears icon_path and re-derives from scratch.
Remove the eager refresh. Resolve paths lazily, inline in the
render dispatcher, only for entries whose icon_path is empty (the
signal that they have never been resolved or were just wiped by an
unload). This pays the resolution cost at most once per
entry-per-list-population, eliminating the scroll-storm entirely.
xmb_unload_icon_thumbnail_textures now clears
node->thumbnail_icon.thumbnail_path_data.icon_path[0] per node to
establish the "empty icon_path = needs resolution" signal the
dispatcher relies on. xmb_refresh_visible_icon_paths is no longer
used and is removed; the is_playlist / Images-Music-Video tab
gates it checked internally are reproduced inline at the two
former call sites.
**Input-repeat gated populate defer (replaces reverted animation defer)**
910c8d0 ("defer xmb_populate_dynamic_icons until horizontal tab
animation settles") attempted to skip wasted populate work during
rapid held-left/right tab traversal by deferring until
categories_x_pos reached its target. Per-frame user screenshots
showed that predicate caused a visible delay between the end of
the enter-playlist animation and the appearance of per-entry
thumbnails — for a few frames the list renders with console-logo
fallbacks before swapping to real thumbnails.
The animation-state predicate is the wrong signal:
1. It only tracks the horizontal tab-row slide, which does not
run when the user navigates into a playlist (depth change, not
tab change). On that path the predicate fires immediately, so
the defer contributes nothing for the entering-playlist case.
2. On the held-left/right tab traversal case the defer was
actually meant to address, the predicate fires when the
last-in-flight animation completes — not when input actually
settles. That can still be during a rapid-mash window, so the
populate still runs on tabs the user blew past.
Switch the predicate to menu_st->scroll.acceleration == 0. That
field increments when a navigation button's held-repeat fires and
resets to 0 on release — the system's existing authoritative
answer to "is the user currently mid-mash?"
Consequences:
- Single-press navigation: acceleration is 0 at the
populate_entries call. Work runs synchronously, same frame as
the action. No defer, no delay.
- Held-repeat traversal: acceleration is > 0 on intermediate tabs.
populate_entries sets the flag instead of doing the work. On
the frame after release, acceleration drops to 0 and
xmb_render fires the deferred populate once, for the tab the
user actually stopped on.
- Context reset and context destroy still clear the flag so the
synchronous populate in xmb_context_reset_internal isn't
duplicated by a deferred fire.1 parent a52ea1b commit f340815
1 file changed
Lines changed: 141 additions & 177 deletions
0 commit comments