MENU/MATERIALUI: Snapshot texture handles to prevent torn reads under threaded video

Icon and background textures in materialui_handle are written by
async load callbacks on the main thread (via gfx_display_load_icon /
task_push_icon_load during context reset, and video_driver_texture_load
in materialui_load_image) and read by materialui_frame and its
sub-functions on the video thread.

Fix: at the top of materialui_frame, memcpy the texture list and
background handle into stack locals:

  tex_list[MUI_TEXTURE_LAST]  (~576 bytes)
  tex_bg                      (8 bytes)

The following sub-functions receive snapshot pointers:

  materialui_render_background:    tex_bg
  materialui_render_header:        tex_list
  materialui_render_nav_bar:       tex_list (passes through to
    materialui_render_nav_bar_bottom and
    materialui_render_nav_bar_right)

All texture reads within these functions and in materialui_frame
itself (keyboard overlay icon, cursor pointer) use the snapshots.
Total: 22 render-path texture reads converted.

The function-pointer-based entry renderers
(materialui_render_menu_entry_default, _playlist_list,
_playlist_dual_icon, _playlist_desktop, _savestate_list) still
read the live struct.  Changing their shared function pointer
signature would cascade to 5 implementations.  They pass handles
by value to materialui_draw_icon which has an existing
null-texture guard (if (!texture) return), preventing crashes
from stale handles.

Additional hardening:

- static float color_white[16] marked static const (never
  mutated, was incorrectly missing const qualifier).
- materialui_draw_icon color parameter changed from float *
  to const float * for const-correctness.

Stack cost: MUI_TEXTURE_LAST * sizeof(uintptr_t) + sizeof(uintptr_t)
= ~584 bytes on 64-bit.

No behavioral or visual changes.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-16 05:31:04 +02:00
1 parent 89fbdfa43f
commit 303168b6fe
1 file changed
+45 -29
+45 -29
View File
@@ -2584,7 +2584,7 @@ static void materialui_draw_icon(
uintptr_t texture,
float x, float y,
float rotation, float scale_factor,
float *color,
const float *color,
math_matrix_4x4 *mymat)
{
gfx_display_ctx_draw_t draw;
@@ -4327,7 +4327,7 @@ static void materialui_render_menu_entry_default(
gfx_display_t *p_disp = disp_get_ptr();
uico_driver_state_t *uico_st = uico_state_get_ptr();
static float color_white[16] = {
static const float color_white[16] = {
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
@@ -6148,6 +6148,7 @@ static size_t materialui_list_get_size(void *data, enum menu_list_type type)
static void materialui_render_background(
materialui_handle_t *mui,
uintptr_t tex_bg,
gfx_display_t *p_disp,
void *userdata,
unsigned video_width, unsigned video_height,
@@ -6183,9 +6184,9 @@ static void materialui_render_background(
draw.color = draw_color;
draw.texture = 0;
if (mui->textures.bg && !libretro_running)
if (tex_bg && !libretro_running)
{
draw.texture = mui->textures.bg;
draw.texture = tex_bg;
/* We are showing a wallpaper - set opacity
* override to menu_wallpaper_opacity */
@@ -6462,6 +6463,7 @@ static void materialui_render_entry_touch_feedback(
static void materialui_render_header(
materialui_handle_t *mui,
const uintptr_t *tex_list,
struct menu_state *menu_st,
menu_list_t *menu_list,
settings_t *settings,
@@ -6582,27 +6584,27 @@ static void materialui_render_header(
/* Set critical by default, to ensure texture_battery
* is always valid */
uintptr_t texture_battery =
mui->textures.list[MUI_TEXTURE_BATTERY_CRITICAL];
tex_list[MUI_TEXTURE_BATTERY_CRITICAL];
/* Draw battery icon */
if (powerstate.charging)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_CHARGING];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_CHARGING];
else
{
if (powerstate.percent >= 100)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_100];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_100];
else if (powerstate.percent >= 90)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_90];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_90];
else if (powerstate.percent >= 80)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_80];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_80];
else if (powerstate.percent >= 60)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_60];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_60];
else if (powerstate.percent >= 50)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_50];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_50];
else if (powerstate.percent >= 30)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_30];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_30];
else if (powerstate.percent >= 20)
texture_battery = mui->textures.list[MUI_TEXTURE_BATTERY_20];
texture_battery = tex_list[MUI_TEXTURE_BATTERY_20];
}
materialui_draw_icon(
@@ -6745,7 +6747,7 @@ static void materialui_render_header(
video_width,
video_height,
mui->icon_size,
mui->textures.list[MUI_TEXTURE_BACK],
tex_list[MUI_TEXTURE_BACK],
0,
(int)mui->sys_bar_height,
0,
@@ -6764,7 +6766,7 @@ static void materialui_render_header(
video_width,
video_height,
mui->icon_size,
mui->textures.list[MUI_TEXTURE_SEARCH],
tex_list[MUI_TEXTURE_SEARCH],
(int)video_width - (int)mui->icon_size - (int)mui->nav_bar_layout_width,
(int)mui->sys_bar_height,
0,
@@ -6785,7 +6787,7 @@ static void materialui_render_header(
video_width,
video_height,
mui->icon_size,
mui->textures.list[MUI_TEXTURE_SWITCH_VIEW],
tex_list[MUI_TEXTURE_SWITCH_VIEW],
(int)video_width - (2 * (int)mui->icon_size)
- (int)mui->nav_bar_layout_width,
(int)mui->sys_bar_height,
@@ -6886,6 +6888,7 @@ static void materialui_render_header(
* we try to handle this with a single function then
* things get incredibly messy and inefficient... */
static void materialui_render_nav_bar_bottom(materialui_handle_t *mui,
const uintptr_t *tex_list,
gfx_display_t *p_disp, void *userdata,
unsigned video_width, unsigned video_height, math_matrix_4x4 *mymat)
{
@@ -6941,7 +6944,7 @@ static void materialui_render_nav_bar_bottom(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[mui->nav_bar.back_tab.texture_index],
tex_list[mui->nav_bar.back_tab.texture_index],
(int)((0.5f * tab_width) - ((float)mui->icon_size / 2.0f)),
nav_bar_y,
0,
@@ -6957,7 +6960,7 @@ static void materialui_render_nav_bar_bottom(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[mui->nav_bar.resume_tab.texture_index],
tex_list[mui->nav_bar.resume_tab.texture_index],
(int)((((float)num_tabs - 0.5f) * tab_width) - ((float)mui->icon_size / 2.0f)),
nav_bar_y,
0,
@@ -6980,7 +6983,7 @@ static void materialui_render_nav_bar_bottom(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[tab->texture_index],
tex_list[tab->texture_index],
(((float)i + 1.5f) * tab_width)
- ((float)mui->icon_size / 2.0f),
nav_bar_y,
@@ -7007,6 +7010,7 @@ static void materialui_render_nav_bar_bottom(materialui_handle_t *mui,
}
static void materialui_render_nav_bar_right(materialui_handle_t *mui,
const uintptr_t *tex_list,
gfx_display_t *p_disp, void *userdata,
unsigned video_width, unsigned video_height,
math_matrix_4x4 *mymat)
@@ -7063,7 +7067,7 @@ static void materialui_render_nav_bar_right(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[mui->nav_bar.back_tab.texture_index],
tex_list[mui->nav_bar.back_tab.texture_index],
nav_bar_x,
(int)((((float)num_tabs - 0.5f) * tab_height) - ((float)mui->icon_size / 2.0f)),
0,
@@ -7079,7 +7083,7 @@ static void materialui_render_nav_bar_right(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[mui->nav_bar.resume_tab.texture_index],
tex_list[mui->nav_bar.resume_tab.texture_index],
nav_bar_x,
(int)((0.5f * tab_height)
- ((float)mui->icon_size / 2.0f)),
@@ -7103,7 +7107,7 @@ static void materialui_render_nav_bar_right(materialui_handle_t *mui,
video_width,
video_height,
mui->icon_size,
mui->textures.list[tab->texture_index],
tex_list[tab->texture_index],
nav_bar_x,
(((float)i + 1.5f) * tab_height)
- ((float)mui->icon_size / 2.0f),
@@ -7130,6 +7134,7 @@ static void materialui_render_nav_bar_right(materialui_handle_t *mui,
}
static void materialui_render_nav_bar(materialui_handle_t *mui,
const uintptr_t *tex_list,
gfx_display_t *p_disp, void *userdata,
unsigned video_width, unsigned video_height,
math_matrix_4x4 *mymat)
@@ -7138,7 +7143,7 @@ static void materialui_render_nav_bar(materialui_handle_t *mui,
{
case MUI_NAV_BAR_LOCATION_RIGHT:
materialui_render_nav_bar_right(
mui, p_disp, userdata, video_width, video_height,
mui, tex_list, p_disp, userdata, video_width, video_height,
mymat);
break;
case MUI_NAV_BAR_LOCATION_HIDDEN:
@@ -7148,7 +7153,7 @@ static void materialui_render_nav_bar(materialui_handle_t *mui,
case MUI_NAV_BAR_LOCATION_BOTTOM:
default:
materialui_render_nav_bar_bottom(
mui, p_disp, userdata, video_width, video_height,
mui, tex_list, p_disp, userdata, video_width, video_height,
mymat);
break;
}
@@ -7878,6 +7883,15 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
if (!mui)
return;
/* Snapshot texture handles — the main thread may write new
* handles via async icon load callbacks or context reset at
* any time. Copy the texture list and bg once and use the
* snapshots for rendering this frame. */
{
uintptr_t tex_list[MUI_TEXTURE_LAST];
uintptr_t tex_bg = mui->textures.bg;
memcpy(tex_list, mui->textures.list, sizeof(tex_list));
/* If menu screensaver is active, draw
* screensaver and return */
if (mui->flags & MUI_FLAG_SHOW_SCREENSAVER)
@@ -7952,7 +7966,7 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
list_x_offset = (int)(mui->transition_x_offset * (float)((int)video_width - (int)mui->nav_bar_layout_width));
/* Draw background */
materialui_render_background(mui, p_disp,
materialui_render_background(mui, tex_bg, p_disp,
userdata,
video_width,
video_height,
@@ -8012,12 +8026,12 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
video_width, video_height, header_height, selection);
/* Draw title + system bar */
materialui_render_header(mui, menu_st, menu_list,
materialui_render_header(mui, tex_list, menu_st, menu_list,
settings, p_disp, userdata,
video_width, video_height, &mymat);
/* Draw navigation bar */
materialui_render_nav_bar(mui, p_disp, userdata,
materialui_render_nav_bar(mui, tex_list, p_disp, userdata,
video_width, video_height, &mymat);
/* Flush second layer of text
@@ -8068,7 +8082,7 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
userdata,
video_width,
video_height,
mui->textures.list[MUI_TEXTURE_KEY_HOVER],
tex_list[MUI_TEXTURE_KEY_HOVER],
mui->font_data.list.font,
input_st->osk_grid,
input_st->osk_ptr,
@@ -8131,7 +8145,7 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
cursor_visible,
color_white,
mui->cursor_size,
mui->textures.list[MUI_TEXTURE_POINTER],
tex_list[MUI_TEXTURE_POINTER],
mui->pointer.x,
mui->pointer.y,
video_width,
@@ -8150,6 +8164,8 @@ static void materialui_frame(void *data, video_frame_info_t *video_info)
if (video_st->current_video && video_st->current_video->set_viewport)
video_st->current_video->set_viewport(
video_st->data, video_width, video_height, false, true);
} /* end of texture snapshot scope */
}
/* Determines current list view type, based on