Skip to content

Commit c0551a8

Browse files
committed
Metal: add GPU Index menu option
The Settings -> Video -> Output "GPU Index" entry was missing when running under the Metal video driver, despite being present for Vulkan and D3D10/11/12. The menu displaylist unconditionally tries to parse VIDEO_GPU_INDEX, but the CONFIG_INT was only registered for Vulkan/D3D, so the entry silently dropped out on Metal. Wire Metal into the existing GPU-index plumbing: - configuration.{h,c}, config.def.h: add metal_gpu_index settings int, DEFAULT_METAL_GPU_INDEX, and SETTING_INT binding for config persistence. All gated on HAVE_METAL. - gfx/video_driver.c: add GFX_CTX_METAL_API slot to gpu_map so video_driver_{set,get}_gpu_api_devices() can store/retrieve the Metal device list. - menu/menu_setting.c: register CONFIG_INT for metal_gpu_index when the active driver is "metal", mirroring the vulkan/d3dN pattern. - menu/cbs/menu_cbs_{left,right}.c: add GFX_CTX_METAL_API wraparound branches so left/right cycles the selected device. - gfx/drivers/metal.m: enumerate adapters and publish the device list to the frontend. Platform split inside metal.m: - macOS: use MTLCopyAllDevices() to enumerate every adapter, honour settings->ints.metal_gpu_index when in range, and fall back to MTLCreateSystemDefaultDevice() with a RARCH_WARN otherwise (same bounds-check + fallback pattern as d3d10.c). - iOS/tvOS: MTLCopyAllDevices() is explicitly API_UNAVAILABLE(ios) and these platforms only ever expose one GPU. Gated behind !defined(HAVE_COCOATOUCH); the CocoaTouch path keeps the existing MTLCreateSystemDefaultDevice() call and publishes a one-element list so the menu entry still renders sensibly. Nothing added raises the minimum deployment target (iOS 6.0 Cocoa TUs don't include metal.m at all; MTLDevice.name and the two device APIs used have been available since the Metal floor of each platform). The GPU list is stored as a plain C struct string_list on a new _gpu_list ivar, so it crosses the ARC/MRC TU boundary safely. MTLCopyAllDevices() and MTLCreateSystemDefaultDevice() are both NS_RETURNS_RETAINED and handled correctly by ARC in metal.m (which is always compiled -fobjc-arc per the per-file Makefile override); string_list_append() strdup's its input so the autoreleased NSString backing -[MTLDevice name].UTF8String is not captured past its lifetime. dealloc frees the list and clears the frontend slot with video_driver_set_gpu_api_devices(GFX_CTX_METAL_API, NULL) before freeing, so a driver reinit can't chase a dangling pointer. Existing retroarch.cfg files without a metal_gpu_index key default to 0 (system default device) and keep their previous behaviour.
1 parent f37ee1a commit c0551a8

8 files changed

Lines changed: 175 additions & 2 deletions

File tree

config.def.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,10 @@
17911791
#define DEFAULT_D3D12_GPU_INDEX 0
17921792
#endif
17931793

1794+
#ifdef HAVE_METAL
1795+
#define DEFAULT_METAL_GPU_INDEX 0
1796+
#endif
1797+
17941798
#if defined(HAKCHI)
17951799
#define DEFAULT_BUILDBOT_SERVER_URL "http://hakchicloud.com/Libretro_Cores/"
17961800
#elif defined(WEBOS)

configuration.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,6 +2794,9 @@ static struct config_int_setting *populate_settings_int(
27942794
#ifdef HAVE_VULKAN
27952795
SETTING_INT("vulkan_gpu_index", &settings->ints.vulkan_gpu_index, true, DEFAULT_VULKAN_GPU_INDEX, false);
27962796
#endif
2797+
#ifdef HAVE_METAL
2798+
SETTING_INT("metal_gpu_index", &settings->ints.metal_gpu_index, true, DEFAULT_METAL_GPU_INDEX, false);
2799+
#endif
27972800

27982801
#ifdef HAVE_NETWORKING
27992802
SETTING_INT("netplay_check_frames", &settings->ints.netplay_check_frames, true, DEFAULT_NETPLAY_CHECK_FRAMES, false);

configuration.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ typedef struct settings
136136
#ifdef HAVE_D3D12
137137
int d3d12_gpu_index;
138138
#endif
139+
#ifdef HAVE_METAL
140+
int metal_gpu_index;
141+
#endif
139142
#ifdef HAVE_WINDOW_OFFSET
140143
int video_window_offset_x;
141144
int video_window_offset_y;

gfx/drivers/metal.m

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include <encodings/utf.h>
3030
#include <compat/strl.h>
31+
#include <lists/string_list.h>
3132
#include <gfx/scaler/scaler.h>
3233
#include <gfx/video_frame.h>
3334
#include <formats/image.h>
@@ -3253,6 +3254,15 @@ @implementation MetalDriver
32533254
* back to the frontend's frame_cache_data to repopulate the texture
32543255
* ourselves. */
32553256
bool _frameEverUploaded;
3257+
3258+
/* List of selectable Metal devices reported to the frontend so the
3259+
* "GPU Index" menu entry can enumerate them. Owned by this driver
3260+
* instance: created in init, published via
3261+
* video_driver_set_gpu_api_devices(), freed in dealloc (where we
3262+
* also clear the frontend pointer). On iOS/tvOS this is always a
3263+
* 1-element list containing the system default device, since
3264+
* MTLCopyAllDevices() is macOS-only. */
3265+
struct string_list *_gpu_list;
32563266
}
32573267

32583268
- (instancetype)initWithVideo:(const video_info_t *)video
@@ -3261,7 +3271,81 @@ - (instancetype)initWithVideo:(const video_info_t *)video
32613271
{
32623272
if (self = [super init])
32633273
{
3264-
_device = MTLCreateSystemDefaultDevice();
3274+
/* Build the list of selectable Metal devices and pick one per the
3275+
* user-configured metal_gpu_index.
3276+
*
3277+
* iOS/tvOS: MTLCopyAllDevices() is macOS-only (explicitly marked
3278+
* API_UNAVAILABLE(ios) in <Metal/MTLDevice.h>), and these platforms
3279+
* only ever expose a single integrated GPU. We publish a
3280+
* one-element list containing the system default device so the
3281+
* frontend's GPU-index menu entry shows something sensible, and
3282+
* the setting is effectively a no-op.
3283+
*
3284+
* macOS: enumerate every adapter via MTLCopyAllDevices(), honour
3285+
* settings->ints.metal_gpu_index if it's in range, and fall back
3286+
* to MTLCreateSystemDefaultDevice() otherwise (matches the
3287+
* behaviour of every other RetroArch driver that exposes this
3288+
* setting — see d3d10.c:2725 for the reference pattern).
3289+
*
3290+
* The list is stored as a plain C struct string_list, so it's
3291+
* safe to cross the ARC/MRC TU boundary; ownership is tracked
3292+
* explicitly and cleared in dealloc. */
3293+
{
3294+
union string_list_elem_attr attr = {0};
3295+
_gpu_list = string_list_new();
3296+
3297+
#if defined(HAVE_COCOATOUCH)
3298+
_device = MTLCreateSystemDefaultDevice();
3299+
3300+
if (_gpu_list && _device)
3301+
{
3302+
const char *name = [[_device name] UTF8String];
3303+
string_list_append(_gpu_list, name ? name : "Default", attr);
3304+
}
3305+
#else
3306+
{
3307+
settings_t *settings_ptr = config_get_ptr();
3308+
int gpu_index = settings_ptr
3309+
? settings_ptr->ints.metal_gpu_index : 0;
3310+
NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices();
3311+
NSUInteger count = devices ? [devices count] : 0;
3312+
NSUInteger i;
3313+
3314+
if (_gpu_list)
3315+
{
3316+
for (i = 0; i < count; i++)
3317+
{
3318+
id<MTLDevice> d = [devices objectAtIndex:i];
3319+
const char *name = [[d name] UTF8String];
3320+
RARCH_LOG("[Metal] Found GPU #%u: \"%s\".\n",
3321+
(unsigned)i, name ? name : "Unknown");
3322+
string_list_append(_gpu_list,
3323+
name ? name : "Unknown", attr);
3324+
}
3325+
}
3326+
3327+
if (count > 0 && gpu_index >= 0 && gpu_index < (int)count)
3328+
{
3329+
const char *picked_name;
3330+
_device = [devices objectAtIndex:gpu_index];
3331+
picked_name = [[_device name] UTF8String];
3332+
RARCH_LOG("[Metal] Using GPU #%d: \"%s\".\n",
3333+
gpu_index, picked_name ? picked_name : "Unknown");
3334+
}
3335+
else
3336+
{
3337+
if (gpu_index != 0)
3338+
RARCH_WARN("[Metal] metal_gpu_index %d out of range (%u device(s) found), "
3339+
"falling back to system default device.\n",
3340+
gpu_index, (unsigned)count);
3341+
_device = MTLCreateSystemDefaultDevice();
3342+
}
3343+
}
3344+
#endif
3345+
3346+
video_driver_set_gpu_api_devices(GFX_CTX_METAL_API, _gpu_list);
3347+
}
3348+
32653349
MetalView *view = (MetalView *)apple_platform.renderView;
32663350
view.device = _device;
32673351
view.delegate = self;
@@ -3422,6 +3506,18 @@ - (void)dealloc
34223506
}
34233507
font_driver_free_osd();
34243508

3509+
/* Tear down the GPU list we published to the frontend. We clear
3510+
* the slot first so any later code paths (e.g. the menu cbs) that
3511+
* look up the list for GFX_CTX_METAL_API see NULL instead of a
3512+
* pointer to freed memory before the next driver instance comes
3513+
* up and repopulates it. */
3514+
if (_gpu_list)
3515+
{
3516+
video_driver_set_gpu_api_devices(GFX_CTX_METAL_API, NULL);
3517+
string_list_free(_gpu_list);
3518+
_gpu_list = NULL;
3519+
}
3520+
34253521
#if METAL_HDR_AVAILABLE
34263522
/* Withdraw the HDR-capability flags so a subsequent driver init (e.g.
34273523
* after the user switches to Vulkan and back) doesn't see stale bits. */

gfx/video_driver.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ static gfx_api_gpu_map gpu_map[] = {
8383
{ NULL, GFX_CTX_VULKAN_API },
8484
{ NULL, GFX_CTX_DIRECT3D10_API },
8585
{ NULL, GFX_CTX_DIRECT3D11_API },
86-
{ NULL, GFX_CTX_DIRECT3D12_API }
86+
{ NULL, GFX_CTX_DIRECT3D12_API },
87+
#ifdef HAVE_METAL
88+
{ NULL, GFX_CTX_METAL_API },
89+
#endif
8790
};
8891

8992
static const video_display_server_t dispserv_null = {

menu/cbs/menu_cbs_left.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,32 @@ static int action_left_video_gpu_index(unsigned type, const char *label,
10801080
break;
10811081
}
10821082
#endif
1083+
#ifdef HAVE_METAL
1084+
case GFX_CTX_METAL_API:
1085+
{
1086+
struct string_list *list = video_driver_get_gpu_api_devices(api);
1087+
1088+
if (list)
1089+
{
1090+
settings_t *settings = config_get_ptr();
1091+
int metal_gpu_index = settings->ints.metal_gpu_index;
1092+
if (metal_gpu_index > 0)
1093+
{
1094+
configuration_set_int(settings,
1095+
settings->ints.metal_gpu_index,
1096+
metal_gpu_index - 1);
1097+
}
1098+
else
1099+
{
1100+
configuration_set_int(settings,
1101+
settings->ints.metal_gpu_index,
1102+
(int)list->size - 1);
1103+
}
1104+
}
1105+
1106+
break;
1107+
}
1108+
#endif
10831109
default:
10841110
break;
10851111
}

menu/cbs/menu_cbs_right.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,23 @@ static int action_right_video_gpu_index(unsigned type, const char *label,
471471
break;
472472
}
473473
#endif
474+
#ifdef HAVE_METAL
475+
case GFX_CTX_METAL_API:
476+
{
477+
struct string_list *list = video_driver_get_gpu_api_devices(api);
478+
479+
if (list)
480+
{
481+
settings_t *settings = config_get_ptr();
482+
if (settings->ints.metal_gpu_index < (int)(list->size - 1))
483+
settings->ints.metal_gpu_index++;
484+
else if (settings->ints.metal_gpu_index == (int)(list->size - 1))
485+
settings->ints.metal_gpu_index = 0;
486+
}
487+
488+
break;
489+
}
490+
#endif
474491
default:
475492
break;
476493
}

menu/menu_setting.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13243,6 +13243,27 @@ static bool setting_append_list(
1324313243
}
1324413244
#endif
1324513245

13246+
#ifdef HAVE_METAL
13247+
if (string_is_equal(video_driver_get_ident(), "metal"))
13248+
{
13249+
CONFIG_INT(
13250+
list, list_info,
13251+
&settings->ints.metal_gpu_index,
13252+
MENU_ENUM_LABEL_VIDEO_GPU_INDEX,
13253+
MENU_ENUM_LABEL_VALUE_VIDEO_GPU_INDEX,
13254+
0,
13255+
&group_info,
13256+
&subgroup_info,
13257+
parent_group,
13258+
general_write_handler,
13259+
general_read_handler);
13260+
menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true);
13261+
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint;
13262+
(*list)[list_info->index - 1].get_string_representation =
13263+
&setting_get_string_representation_int_gpu_index;
13264+
}
13265+
#endif
13266+
1324613267
#ifdef WIIU
1324713268
CONFIG_BOOL(
1324813269
list, list_info,

0 commit comments

Comments
 (0)