@@ -1830,12 +1830,58 @@ static void xmb_update_savestate_thumbnail_image(void *data)
18301830 xmb -> thumbnails .savestate .flags |= GFX_THUMB_FLAG_CORE_ASPECT ;
18311831}
18321832
1833+ /* Walk the visible playlist range and refresh each entry's icon-thumbnail
1834+ * path_data. Returns true if the refresh actually ran (gates all satisfied),
1835+ * so the caller can set `pending_icons` to wake the request dispatcher in
1836+ * xmb_render. The caller is responsible for the `gfx_thumbnail_cancel_pending_requests()`
1837+ * call that should precede this work, because the two existing callers have
1838+ * different cancel strategies:
1839+ *
1840+ * - `xmb_populate_dynamic_icons` calls `xmb_unload_icon_thumbnail_textures`
1841+ * first, which already cancels and clears pending_icons as part of a full
1842+ * texture wipe (used when the displayed list changes).
1843+ *
1844+ * - `xmb_selection_pointer_changed` only cancels — it runs when the cursor
1845+ * moves within an unchanged list, so existing per-node textures stay
1846+ * valid and an unload would be wasteful.
1847+ *
1848+ * That asymmetry is the reason this helper deliberately does not cancel on
1849+ * its own. */
1850+ static bool xmb_refresh_visible_icon_paths (xmb_handle_t * xmb )
1851+ {
1852+ unsigned i , end , height , entry_start , entry_end ;
1853+ struct menu_state * menu_st = menu_state_get_ptr ();
1854+ menu_list_t * menu_list = menu_st -> entries .list ;
1855+ file_list_t * selection_buf = MENU_LIST_GET_SELECTION (menu_list , 0 );
1856+ size_t selection = menu_st -> selection_ptr ;
1857+
1858+ if (!( xmb -> is_playlist
1859+ && gfx_thumbnail_is_enabled (menu_st -> thumbnail_path_data , GFX_THUMBNAIL_ICON )
1860+ && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_IMAGES_TAB ))
1861+ && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_MUSIC_TAB ))
1862+ && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_VIDEO_TAB ))))
1863+ return false;
1864+
1865+ end = (unsigned )selection_buf -> size ;
1866+ video_driver_get_size (NULL , & height );
1867+ xmb_calculate_visible_range (xmb , height , end , (unsigned )selection , & entry_start , & entry_end );
1868+
1869+ for (i = entry_start ; i <= entry_end ; i ++ )
1870+ {
1871+ xmb_node_t * node = (xmb_node_t * )selection_buf -> list [i ].userdata ;
1872+ if (!node )
1873+ continue ;
1874+ xmb_set_dynamic_icon_content (xmb , NULL , i , & node -> thumbnail_icon );
1875+ }
1876+ return true;
1877+ }
1878+
18331879/* Is called when the pointer position changes
18341880 * within a list/sub-list (vertically) */
18351881static void xmb_selection_pointer_changed (
18361882 xmb_handle_t * xmb , bool allow_animations )
18371883{
1838- unsigned i , end , height , entry_start , entry_end ;
1884+ unsigned i , end , height ;
18391885 size_t num = 0 ;
18401886 int threshold = 0 ;
18411887 struct menu_state * menu_st = menu_state_get_ptr ();
@@ -1856,7 +1902,19 @@ static void xmb_selection_pointer_changed(
18561902 menu_st -> entries .begin = num ;
18571903
18581904 video_driver_get_size (NULL , & height );
1859- xmb_calculate_visible_range (xmb , height , end , (unsigned )selection , & entry_start , & entry_end );
1905+
1906+ /* Refresh icon-thumbnail path_data for currently visible playlist
1907+ * entries. The helper itself only writes path_data; xmb_render issues
1908+ * the requests once pending_icons is set. Cancelling first provides
1909+ * "storm protection during fast jumps" — any in-flight requests from
1910+ * the previous cursor position stop being relevant the moment the
1911+ * cursor moves. Skipped entirely on non-playlist lists (helper returns
1912+ * false before touching anything). */
1913+ if (xmb_refresh_visible_icon_paths (xmb ))
1914+ {
1915+ gfx_thumbnail_cancel_pending_requests ();
1916+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
1917+ }
18601918
18611919 for (i = 0 ; i < end ; i ++ )
18621920 {
@@ -1871,23 +1929,6 @@ static void xmb_selection_pointer_changed(
18711929 iy = xmb_item_y (xmb , i , selection );
18721930 real_iy = iy + xmb -> margins_screen_top ;
18731931
1874- if ( xmb -> is_playlist
1875- && gfx_thumbnail_is_enabled (menu_st -> thumbnail_path_data , GFX_THUMBNAIL_ICON )
1876- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_IMAGES_TAB ))
1877- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_MUSIC_TAB ))
1878- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_VIDEO_TAB ))
1879- )
1880- {
1881- xmb_icons_t * thumbnail_icon = & node -> thumbnail_icon ;
1882- if (i >= entry_start && i <= entry_end )
1883- {
1884- /* Playlist updates */
1885- xmb_set_dynamic_icon_content (xmb , NULL , i , thumbnail_icon );
1886- gfx_thumbnail_cancel_pending_requests ();
1887- xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
1888- }
1889- }
1890-
18911932 if (i == selection )
18921933 {
18931934 unsigned depth = (unsigned )xmb_list_get_size (xmb , MENU_LIST_PLAIN );
@@ -2704,44 +2745,20 @@ static void xmb_tab_set_selection(void *data)
27042745
27052746static void xmb_populate_dynamic_icons (xmb_handle_t * xmb )
27062747{
2707- unsigned i , entry_start , entry_end , height ;
2708- struct menu_state * menu_st = menu_state_get_ptr ();
2709- menu_list_t * menu_list = menu_st -> entries .list ;
2710- file_list_t * selection_buf = MENU_LIST_GET_SELECTION (menu_list , 0 );
2711- unsigned end = (unsigned )selection_buf -> size ;
2712- size_t selection = menu_st -> selection_ptr ;
2713-
2714- if (gfx_thumbnail_is_enabled (menu_st -> thumbnail_path_data , GFX_THUMBNAIL_ICON ))
2715- {
2716- /* Clear current textures if they are there */
2717- xmb_unload_icon_thumbnail_textures (xmb );
2718-
2719- entry_start = 0 ;
2720- entry_end = end ;
2748+ struct menu_state * menu_st = menu_state_get_ptr ();
27212749
2722- video_driver_get_size (NULL , & height );
2723- xmb_calculate_visible_range (xmb , height , end , (unsigned )selection , & entry_start , & entry_end );
2724- if ( xmb -> is_playlist
2725- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_IMAGES_TAB ))
2726- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_MUSIC_TAB ))
2727- && !string_is_equal (xmb -> title_name , msg_hash_to_str (MENU_ENUM_LABEL_VALUE_VIDEO_TAB ))
2728- )
2729- {
2730- for (i = entry_start ; i <= entry_end ; i ++ )
2731- {
2732- xmb_icons_t * thumbnail_icon ;
2733- xmb_node_t * node = (xmb_node_t * )selection_buf -> list [i ].userdata ;
2750+ if (!gfx_thumbnail_is_enabled (menu_st -> thumbnail_path_data , GFX_THUMBNAIL_ICON ))
2751+ return ;
27342752
2735- if (!node )
2736- continue ;
2753+ /* Used when the displayed list changes (new playlist pushed or context
2754+ * reset): the previous list's per-node icon textures are stale, so wipe
2755+ * them before queuing fresh requests. `xmb_unload_icon_thumbnail_textures`
2756+ * handles the cancel and clears `pending_icons` as part of the wipe;
2757+ * the helper below only needs to write fresh path_data. */
2758+ xmb_unload_icon_thumbnail_textures (xmb );
27372759
2738- thumbnail_icon = & node -> thumbnail_icon ;
2739- xmb_set_dynamic_icon_content (xmb , NULL , i , thumbnail_icon );
2740- gfx_thumbnail_cancel_pending_requests ();
2741- xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
2742- }
2743- }
2744- }
2760+ if (xmb_refresh_visible_icon_paths (xmb ))
2761+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
27452762}
27462763
27472764static void xmb_list_switch (xmb_handle_t * xmb )
0 commit comments