Skip to content

Commit 4e17090

Browse files
author
龙虾机器人
committed
Fix XMB thumbnail memory exhaustion. Limits concurrent loads, adds scroll detection, frees textures. Fixes #6747
1 parent 82c5e3b commit 4e17090

3 files changed

Lines changed: 149 additions & 38 deletions

File tree

gfx/gfx_thumbnail.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
5353
return &gfx_thumb_st;
5454
}
5555

56+
/* LOW-MEMORY FIX: Concurrent load management */
57+
58+
void gfx_thumbnail_set_max_concurrent_loads(unsigned max_loads)
59+
{
60+
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
61+
p_gfx_thumb->max_concurrent_loads = max_loads;
62+
}
63+
64+
unsigned gfx_thumbnail_get_concurrent_loads(void)
65+
{
66+
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
67+
return p_gfx_thumb->current_loads;
68+
}
69+
70+
bool gfx_thumbnail_can_start_load(void)
71+
{
72+
gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
73+
74+
if (p_gfx_thumb->max_concurrent_loads == 0)
75+
return true;
76+
77+
return p_gfx_thumb->current_loads < p_gfx_thumb->max_concurrent_loads;
78+
}
79+
5680
/* Setters */
5781

5882
/* When streaming thumbnails, sets time in ms that an
@@ -152,6 +176,9 @@ static void gfx_thumbnail_handle_upload(
152176
if (!thumbnail_tag)
153177
goto end;
154178

179+
/* LOW-MEMORY FIX: Decrement concurrent load counter */
180+
p_gfx_thumb->current_loads--;
181+
155182
/* Ensure that we are operating on the correct
156183
* thumbnail... */
157184
if (thumbnail_tag->list_id != p_gfx_thumb->list_id)
@@ -295,6 +322,10 @@ void gfx_thumbnail_request(
295322
if (!path_data || !thumbnail)
296323
return;
297324

325+
/* LOW-MEMORY FIX: Check if we can start a new load */
326+
if (!gfx_thumbnail_can_start_load())
327+
return;
328+
298329
/* Reset thumbnail, then set 'missing' status by default
299330
* (saves a number of checks later) */
300331
gfx_thumbnail_reset(thumbnail);
@@ -317,6 +348,9 @@ void gfx_thumbnail_request(
317348
if (!thumbnail_tag)
318349
goto end;
319350

351+
/* LOW-MEMORY FIX: Increment concurrent load counter */
352+
p_gfx_thumb->current_loads++;
353+
320354
/* Configure user data */
321355
thumbnail_tag->thumbnail = thumbnail;
322356
thumbnail_tag->list_id = p_gfx_thumb->list_id;

gfx/gfx_thumbnail.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ struct gfx_thumbnail_state
127127
/* When true, 'fade in' animation will also be
128128
* triggered for missing thumbnails */
129129
bool fade_missing;
130+
131+
/* LOW-MEMORY FIX: Maximum number of concurrent thumbnail load tasks
132+
* Helps prevent memory exhaustion on low-memory devices (RPi, Switch, etc.) */
133+
unsigned max_concurrent_loads;
134+
unsigned current_loads;
130135
};
131136

132137
typedef struct gfx_thumbnail_state gfx_thumbnail_state_t;
@@ -151,6 +156,20 @@ void gfx_thumbnail_set_fade_duration(float duration);
151156
* any 'thumbnail unavailable' notifications */
152157
void gfx_thumbnail_set_fade_missing(bool fade_missing);
153158

159+
/* LOW-MEMORY FIX: Concurrent load management API */
160+
161+
/* Sets maximum number of concurrent thumbnail load tasks
162+
* allowed at any time. Helps prevent memory exhaustion on
163+
* low-memory devices.
164+
* > Use 0 for unlimited (default behavior) */
165+
void gfx_thumbnail_set_max_concurrent_loads(unsigned max_loads);
166+
167+
/* Returns current number of active thumbnail load tasks */
168+
unsigned gfx_thumbnail_get_concurrent_loads(void);
169+
170+
/* Returns whether a new thumbnail load can be started */
171+
bool gfx_thumbnail_can_start_load(void);
172+
154173
/* Core interface */
155174

156175
/* When called, prevents the handling of any pending

menu/drivers/xmb.c

Lines changed: 96 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)