Files
ARMSX2/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm
T
Jeen 14fc29c0cd iOS: gate macOS-only APIs behind TARGET_OS_IPHONE and add frontend link stubs
Gate all macOS-only system APIs behind TARGET_OS_IPHONE checks across the shared core: ApplicationServices, IOKit, mouse APIs, AppKit, MetalFX, CDVD Darwin sources, USB, discord-rpc, cubeb CoreAudio HAL, and BSD networking headers in DEV9. Port TARGET_OS_IPHONE guards for DEV9 AdapterUtils with iOS fallbacks for sockaddr_dl, rt_msghdr, and sysctl. Add ARMSX2_ROOT to the include path for the frontend's common include style. Drop the global _M_ARM64 define that triggered fast_float MSVC intrin.h inclusion since __aarch64__ covers the core paths. Gate MTLFeatureSet_macOS_GPUFamily1_v1 behind !TARGET_OS_IPHONE. Fix missing unistd.h and gate pthread_jit_write_protect_np which is unavailable on iOS. Add stubs for DarwinMisc JIT diagnostics, Achievements stats and info APIs, Discord_Register, Host capture callbacks, and the host hotkeys map so the frontend links cleanly. Merge upstream master.
2026-07-12 16:38:59 +02:00

265 lines
10 KiB
Plaintext

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "GSMTLDeviceInfo.h"
#include "GS/GS.h"
#include "common/Console.h"
#include "common/Path.h"
#ifdef __APPLE__
static id<MTLLibrary> loadMainLibrary(id<MTLDevice> dev, NSString* name)
{
NSString* path = [[NSBundle mainBundle] pathForResource:name ofType:@"metallib"];
if (!path)
{
std::string ssname = std::string([name UTF8String]) + ".metallib";
std::string sspath = Path::Combine(EmuFolders::Resources, ssname);
path = [[NSString alloc] initWithBytes:sspath.data() length:sspath.length() encoding:NSUTF8StringEncoding];
}
return path ? [dev newLibraryWithFile:path error:nullptr] : nullptr;
}
static MRCOwned<id<MTLLibrary>> loadMainLibrary(id<MTLDevice> dev)
{
if (@available(macOS 11.0, iOS 14.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal23"))
return MRCTransfer(lib);
if (@available(macOS 10.15, iOS 13.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal22"))
return MRCTransfer(lib);
if (@available(macOS 10.14, iOS 12.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal21"))
return MRCTransfer(lib);
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"default"))
return MRCTransfer(lib);
return MRCTransfer([dev newDefaultLibrary]);
}
static GSMTLDevice::MetalVersion detectLibraryVersion(id<MTLLibrary> lib)
{
// These functions are defined in tfx.metal to indicate the metal version used to make the metallib
if (MRCTransfer([lib newFunctionWithName:@"metal_version_23"]))
return GSMTLDevice::MetalVersion::Metal23;
if (MRCTransfer([lib newFunctionWithName:@"metal_version_22"]))
return GSMTLDevice::MetalVersion::Metal22;
if (MRCTransfer([lib newFunctionWithName:@"metal_version_21"]))
return GSMTLDevice::MetalVersion::Metal21;
return GSMTLDevice::MetalVersion::Metal20;
}
static bool detectPrimIDSupport(id<MTLDevice> dev, id<MTLLibrary> lib)
{
// Nvidia Metal driver is missing primid support, yay
MRCOwned<MTLRenderPipelineDescriptor*> desc = MRCTransfer([MTLRenderPipelineDescriptor new]);
[desc setVertexFunction:MRCTransfer([lib newFunctionWithName:@"fs_triangle"])];
[desc setFragmentFunction:MRCTransfer([lib newFunctionWithName:@"primid_test"])];
[[desc colorAttachments][0] setPixelFormat:MTLPixelFormatR8Uint];
NSError* err;
[[dev newRenderPipelineStateWithDescriptor:desc error:&err] release];
return !err;
}
namespace
{
enum class DetectionResult
{
HaswellOrNotIntel, ///< Everything works fine
Broadwell, ///< PrimID broken
Skylake, ///< PrimID broken, FBFetch supported
};
}
static DetectionResult detectIntelGPU(id<MTLDevice> dev, id<MTLLibrary> lib)
{
// Even though it's nowhere in the feature set tables, some Intel GPUs support fbfetch!
// Annoyingly, the Haswell compiler successfully makes a pipeline but actually miscompiles it and doesn't insert any fbfetch instructions
// The Broadwell compiler inserts the Skylake fbfetch instruction, but Broadwell doesn't support that. It seems to make the shader not do anything
// So we actually have to test the thing
// In addition, Broadwell+ has broken primid so we need to disable that.
// Conveniently we can use the same test to detect both (except on macOS < 11. All Broadwell machines support 11, so the answer to that is "upgrade")
// See https://github.com/tellowkrinkle/MetalBugReproduction/releases/tag/BrokenPrimID for details
// AMD compiler crashes and gets retried 3 times over multiple seconds trying to compile the pipeline
// We know this is only a possibility on Intel anyways
if (![[dev name] containsString:@"Intel"])
return DetectionResult::HaswellOrNotIntel;
auto pdesc = MRCTransfer([MTLRenderPipelineDescriptor new]);
[pdesc setVertexFunction:MRCTransfer([lib newFunctionWithName:@"fs_triangle"])];
[pdesc setFragmentFunction:MRCTransfer([lib newFunctionWithName:@"fbfetch_test"])];
[[pdesc colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA8Unorm];
auto pipe = MRCTransfer([dev newRenderPipelineStateWithDescriptor:pdesc error:nil]);
if (!pipe)
return DetectionResult::HaswellOrNotIntel;
auto buf = MRCTransfer([dev newBufferWithLength:4 options:MTLResourceStorageModeShared]);
auto tdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:1 height:1 mipmapped:false];
[tdesc setUsage:MTLTextureUsageRenderTarget];
auto tex = MRCTransfer([dev newTextureWithDescriptor:tdesc]);
auto q = MRCTransfer([dev newCommandQueue]);
u32 px = 0x11223344;
memcpy([buf contents], &px, 4);
id<MTLCommandBuffer> cmdbuf = [q commandBuffer];
id<MTLBlitCommandEncoder> upload = [cmdbuf blitCommandEncoder];
[upload copyFromBuffer:buf sourceOffset:0 sourceBytesPerRow:4 sourceBytesPerImage:4 sourceSize:MTLSizeMake(1, 1, 1) toTexture:tex destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(0, 0, 0)];
[upload endEncoding];
auto rpdesc = MRCTransfer([MTLRenderPassDescriptor new]);
[[rpdesc colorAttachments][0] setTexture:tex];
[[rpdesc colorAttachments][0] setLoadAction:MTLLoadActionLoad];
[[rpdesc colorAttachments][0] setStoreAction:MTLStoreActionStore];
id<MTLRenderCommandEncoder> renc = [cmdbuf renderCommandEncoderWithDescriptor:rpdesc];
[renc setRenderPipelineState:pipe];
[renc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
[renc endEncoding];
id<MTLBlitCommandEncoder> download = [cmdbuf blitCommandEncoder];
[download copyFromTexture:tex sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0, 0, 0) sourceSize:MTLSizeMake(1, 1, 1) toBuffer:buf destinationOffset:0 destinationBytesPerRow:4 destinationBytesPerImage:4];
[download endEncoding];
[cmdbuf commit];
[cmdbuf waitUntilCompleted];
u32 outpx;
memcpy(&outpx, [buf contents], 4);
// Proper fbfetch will double contents, Haswell will return black, and Broadwell will do nothing
if (outpx == 0x22446688)
return DetectionResult::Skylake;
else if (outpx == 0x11223344)
return DetectionResult::Broadwell;
else
return DetectionResult::HaswellOrNotIntel;
}
GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
{
if (!dev)
return;
shaders = loadMainLibrary(dev);
memset(&features, 0, sizeof(features));
if (char* env = getenv("MTL_UNIFIED_MEMORY"))
features.unified_memory = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
else if (@available(macOS 10.15, iOS 13.0, *))
features.unified_memory = [dev hasUnifiedMemory];
else
features.unified_memory = false;
if (@available(macOS 10.15, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyMac2] || [dev supportsFamily:MTLGPUFamilyApple1])
features.texture_swizzle = true;
if (@available(macOS 11.0, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyApple1])
features.framebuffer_fetch = features.memoryless_textures = true;
if (@available(macOS 10.15, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyMac2] || [dev supportsFamily:MTLGPUFamilyApple1])
features.has_fast_half = true; // Approximate guess
features.shader_version = detectLibraryVersion(shaders);
if (features.framebuffer_fetch && features.shader_version < MetalVersion::Metal23)
{
Console.Warning("Metal: GPU supports framebuffer fetch but shader lib does not! Get an updated shader lib for better performance!");
features.framebuffer_fetch = false;
}
features.primid = features.shader_version >= MetalVersion::Metal22;
if (features.primid && !detectPrimIDSupport(dev, shaders))
features.primid = false;
features.depth_feedback = false;
features.rov = [dev areRasterOrderGroupsSupported];
features.rov_requires_r32 = [dev readWriteTextureSupport] < MTLReadWriteTextureTier2;
NSString* name = [dev name];
if ([name containsString:@"Intel"])
{
if (!features.framebuffer_fetch && features.shader_version >= MetalVersion::Metal23)
{
switch (detectIntelGPU(dev, shaders))
{
case DetectionResult::HaswellOrNotIntel:
// Older Intel GPUs seem to be fine with depth feedback
features.depth_feedback = true;
break;
case DetectionResult::Broadwell:
features.primid = false; // Broken
break;
case DetectionResult::Skylake:
features.primid = false; // Broken
features.framebuffer_fetch = true;
break;
}
}
}
else if ([name containsString:@"AMD"])
{
// RDNA+ seems to work fine with depth feedback
if (@available(macOS 13, iOS 16, *))
if ([dev supportsFamily:MTLGPUFamilyMetal3])
features.depth_feedback = true;
// ROV doesn't work on AMD GPUs with no render target
// Ideally the driver would insert a dummy texture for us (it does on Linux) but the Metal driver doesn't.
features.rov_requires_rt = true;
}
else if ([name containsString:@"NVIDIA"])
{
// macOS only supports Kepler, which seems to work fine with depth feedback
features.depth_feedback = true;
}
else if ([name containsString:@"Apple"])
{
// No special settings
}
else
{
Console.Warning("Unrecognized GPU vendor %s", [name UTF8String]);
}
if (features.framebuffer_fetch && GSConfig.DisableFramebufferFetch)
{
Console.Warning("Framebuffer fetch was found but is disabled. This will reduce performance.");
features.framebuffer_fetch = false;
}
if (char* env = getenv("MTL_SLOW_COLOR_COMPRESSION"))
features.slow_color_compression = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
else
features.slow_color_compression = [[dev name] containsString:@"AMD"] || [[dev name] isEqualToString:@"Intel HD Graphics 4000"];
if (char* env = getenv("MTL_ROV_WITH_RT"))
features.rov_requires_rt = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
features.max_texsize = GetMaxTextureSize(dev);
this->dev = std::move(dev);
}
u32 GSMTLDevice::GetMaxTextureSize(id<MTLDevice> dev)
{
if (@available(macOS 10.15, iOS 13.0, *))
{
MTLGPUFamily apple10 = static_cast<MTLGPUFamily>(1010); // Avoid relying on latest SDK, define ourselves
if ([dev supportsFamily:apple10])
return 32768;
if ([dev supportsFamily:MTLGPUFamilyApple3])
return 16384;
}
#if !TARGET_OS_IPHONE
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
return 16384;
#endif
return 8192;
}
const char* to_string(GSMTLDevice::MetalVersion ver)
{
switch (ver)
{
case GSMTLDevice::MetalVersion::Metal20: return "Metal 2.0";
case GSMTLDevice::MetalVersion::Metal21: return "Metal 2.1";
case GSMTLDevice::MetalVersion::Metal22: return "Metal 2.2";
case GSMTLDevice::MetalVersion::Metal23: return "Metal 2.3";
}
}
#endif // __APPLE__