@@ -6847,6 +6847,12 @@ static void xmb_render(void *data,
68476847 unsigned last = (unsigned )end ;
68486848 unsigned gfx_thumbnail_upscale_threshold = settings -> uints .gfx_thumbnail_upscale_threshold ;
68496849 bool network_on_demand_thumbnails = settings -> bools .network_on_demand_thumbnails ;
6850+ /* LOW-MEMORY FIX: Variables for rapid scroll detection
6851+ * Must be declared at block start for C89 compliance */
6852+ static retro_time_t last_scroll_time = 0 ;
6853+ static unsigned scroll_count = 0 ;
6854+ retro_time_t current_time ;
6855+ bool rapid_scroll ;
68506856
68516857 if (!xmb )
68526858 return ;
@@ -7150,57 +7156,99 @@ static void xmb_render(void *data,
71507156 /* Handle any pending icon thumbnail load requests */
71517157 if (xmb -> thumbnails .pending_icons != XMB_PENDING_THUMBNAIL_NONE )
71527158 {
7153- /* Limit image loading per frame to prevent slowdowns,
7154- * and hide the usual icon while pending */
7155- uint8_t max_per_frame = 2 ;
7156- uint8_t cur_per_frame = 0 ;
7159+ /* LOW-MEMORY FIX: Detect rapid scrolling and defer thumbnail loading
7160+ * This prevents texture exhaustion on devices like Raspberry Pi and Switch */
7161+ current_time = cpu_features_get_time_usec ();
71577162
7158- /* Based on height of screen calculate the available entries that are visible */
7159- if (height )
7160- xmb_calculate_visible_range (xmb , height , end , (unsigned )selection , & first , & last );
7163+ /* Count scrolls within 100ms window */
7164+ if (current_time - last_scroll_time < 100000 )
7165+ scroll_count ++ ;
7166+ else
7167+ scroll_count = 1 ;
71617168
7162- xmb -> thumbnails . pending_icons = XMB_PENDING_THUMBNAIL_NONE ;
7169+ last_scroll_time = current_time ;
71637170
7164- for (i = first ; i <= last ; i ++ )
7171+ /* If scrolling rapidly (>5 scrolls in 100ms), skip thumbnail loading */
7172+ rapid_scroll = (scroll_count > 5 );
7173+
7174+ if (!rapid_scroll )
71657175 {
7166- xmb_node_t * node = (xmb_node_t * )selection_buf -> list [i ].userdata ;
7167- xmb_icons_t * thumbnail_icon ;
7176+ /* Limit image loading per frame to prevent slowdowns,
7177+ * and hide the usual icon while pending */
7178+ uint8_t max_per_frame = 2 ;
7179+ uint8_t cur_per_frame = 0 ;
71687180
7169- if (!node )
7170- continue ;
7181+ /* Based on height of screen calculate the available entries that are visible */
7182+ if (height )
7183+ xmb_calculate_visible_range (xmb , height , end , (unsigned )selection , & first , & last );
71717184
7172- thumbnail_icon = & node -> thumbnail_icon ;
7185+ xmb -> thumbnails . pending_icons = XMB_PENDING_THUMBNAIL_NONE ;
71737186
7174- if ( cur_per_frame >= max_per_frame )
7187+ for ( i = first ; i <= last ; i ++ )
71757188 {
7176- node -> icon_hide = true;
7177- xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
7178- continue ;
7179- }
7189+ xmb_node_t * node = (xmb_node_t * )selection_buf -> list [i ].userdata ;
7190+ xmb_icons_t * thumbnail_icon ;
7191+
7192+ if (!node )
7193+ continue ;
7194+
7195+ thumbnail_icon = & node -> thumbnail_icon ;
7196+
7197+ /* LOW-MEMORY FIX: Skip loading if already at max concurrent loads */
7198+ if (!gfx_thumbnail_can_start_load ())
7199+ {
7200+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
7201+ continue ;
7202+ }
7203+
7204+ if (cur_per_frame >= max_per_frame )
7205+ {
7206+ node -> icon_hide = true;
7207+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
7208+ continue ;
7209+ }
7210+
7211+ if ( thumbnail_icon -> icon .status == GFX_THUMBNAIL_STATUS_UNKNOWN
7212+ && !string_is_empty (thumbnail_icon -> thumbnail_path_data .icon_path ))
7213+ {
7214+ node -> icon_hide = false;
7215+ if (!xmb_load_dynamic_icon (
7216+ thumbnail_icon -> thumbnail_path_data .icon_path ,
7217+ & thumbnail_icon -> icon ))
7218+ {
7219+ gfx_thumbnail_request_stream (
7220+ & thumbnail_icon -> thumbnail_path_data ,
7221+ p_anim ,
7222+ GFX_THUMBNAIL_ICON ,
7223+ playlist , i ,
7224+ & thumbnail_icon -> icon ,
7225+ gfx_thumbnail_upscale_threshold ,
7226+ network_on_demand_thumbnails );
7227+ }
7228+ else
7229+ cur_per_frame ++ ;
7230+ }
71807231
7181- if ( thumbnail_icon -> icon .status == GFX_THUMBNAIL_STATUS_UNKNOWN
7182- && !string_is_empty (thumbnail_icon -> thumbnail_path_data .icon_path ))
7232+ if (thumbnail_icon -> icon .status == GFX_THUMBNAIL_STATUS_UNKNOWN )
7233+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
7234+ }
7235+ }
7236+ else
7237+ {
7238+ /* During rapid scroll, reset off-screen thumbnails to free memory */
7239+ size_t sel = menu_st -> selection_ptr ;
7240+ for (i = first ; i <= last ; i ++ )
71837241 {
7184- node -> icon_hide = false;
7185- if (!xmb_load_dynamic_icon (
7186- thumbnail_icon -> thumbnail_path_data .icon_path ,
7187- & thumbnail_icon -> icon ))
7242+ if (i != sel )
71887243 {
7189- gfx_thumbnail_request_stream (
7190- & thumbnail_icon -> thumbnail_path_data ,
7191- p_anim ,
7192- GFX_THUMBNAIL_ICON ,
7193- playlist , i ,
7194- & thumbnail_icon -> icon ,
7195- gfx_thumbnail_upscale_threshold ,
7196- network_on_demand_thumbnails );
7244+ xmb_node_t * other_node = (xmb_node_t * )selection_buf -> list [i ].userdata ;
7245+ if (other_node )
7246+ gfx_thumbnail_reset (& other_node -> thumbnail_icon .icon );
71977247 }
7198- else
7199- cur_per_frame ++ ;
72007248 }
7201-
7202- if ( thumbnail_icon -> icon . status == GFX_THUMBNAIL_STATUS_UNKNOWN )
7203- xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
7249+
7250+ /* Still need to retry later */
7251+ xmb -> thumbnails .pending_icons = XMB_PENDING_THUMBNAIL_ICONS ;
72047252 }
72057253 }
72067254 else if (xmb -> thumbnails .icon .status == GFX_THUMBNAIL_STATUS_UNKNOWN
@@ -9180,6 +9228,16 @@ static void *xmb_init(void **userdata, bool video_is_threaded)
91809228 gfx_thumbnail_set_fade_duration (-1.0f );
91819229 gfx_thumbnail_set_fade_missing (false);
91829230
9231+ /* LOW-MEMORY FIX: Set platform-appropriate concurrent load limits
9232+ * Prevents texture memory exhaustion on RPi, Switch, and other low-RAM devices */
9233+ #if defined(HAVE_OPENGLES ) || defined(__SWITCH__ ) || defined(ANDROID )
9234+ /* Low-memory platforms: limit to 2 concurrent loads */
9235+ gfx_thumbnail_set_max_concurrent_loads (2 );
9236+ #else
9237+ /* Desktop platforms: allow 4 concurrent loads */
9238+ gfx_thumbnail_set_max_concurrent_loads (4 );
9239+ #endif
9240+
91839241 xmb -> use_ps3_layout =
91849242 xmb_use_ps3_layout (settings -> uints .menu_xmb_layout , width , height );
91859243 xmb -> last_use_ps3_layout = xmb -> use_ps3_layout ;
0 commit comments