diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 4401cc6c7f..cdd8b7b79a 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1485,8 +1485,12 @@ if (APPLE) ${COREMEDIA_LIBRARY} ) endif() - # Weak-link MetalFX so older OS versions still load; each use is @available-guarded. - target_link_options(PCSX2_FLAGS INTERFACE "SHELL:-weak_framework MetalFX") + # Weak-link MetalFX so older OS versions still load. Skip on the iOS simulator: + # MetalFX.framework isn't in the iphonesimulator SDK (see PCSX2_HAS_METALFX in + # GSDeviceMTL.h), so linking it there fails with "framework not found". + if(ARMSX2_REAL_DEVICE OR NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") + target_link_options(PCSX2_FLAGS INTERFACE "SHELL:-weak_framework MetalFX") + endif() endif() set_property(GLOBAL PROPERTY PCSX2_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index 434e3005aa..38bb32ae1f 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -27,10 +27,15 @@ using GSMTLView = UIView; #include using GSMTLView = NSView; #endif -// MetalFX spatial upscaler ships on macOS 13+ and iOS 16+. It is weak-linked -// (see CMakeLists.txt) so the binary still loads on older OS revisions, and -// every call site is guarded by @available plus a runtime supportsDevice: probe. -#include +// MetalFX upscaler: macOS 13+ / iOS 16+, weak-linked on device. The simulator +// SDK has no MetalFX headers, so PCSX2_HAS_METALFX is 0 there and every +// MetalFX reference is compiled out; m_features.metalfx_spatial stays false. +#if TARGET_OS_SIMULATOR + #define PCSX2_HAS_METALFX 0 +#else + #define PCSX2_HAS_METALFX 1 + #include +#endif #include #include #include @@ -265,10 +270,12 @@ public: // MetalFX spatial upscaler. Creating the scaler is expensive, so it's cached and // only rebuilt when the input/output size or format changes (the cache key below). - // Available macOS 13+ / iOS 16+; weak-linked so this compiles on all targets. + // macOS 13+ / iOS 16+ device, weak-linked. Compiled out on the simulator. +#if PCSX2_HAS_METALFX API_AVAILABLE(macos(13.0), ios(16.0)) MRCOwned> m_mfx_spatial; int m_mfx_in_w = 0, m_mfx_in_h = 0, m_mfx_out_w = 0, m_mfx_out_h = 0; MTLPixelFormat m_mfx_in_fmt = MTLPixelFormatInvalid, m_mfx_out_fmt = MTLPixelFormatInvalid; +#endif std::vector>> m_convert_pipeline; MRCOwned> m_present_pipeline[static_cast(PresentShader::Count)]; MRCOwned> m_merge_pipeline[4]; @@ -416,7 +423,12 @@ public: bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) override; /// (Re)builds m_mfx_spatial when the src/dst size or format changes. Returns false on failure. + /// On simulator builds (PCSX2_HAS_METALFX=0) this is a no-op stub returning false. +#if PCSX2_HAS_METALFX API_AVAILABLE(macos(13.0), ios(16.0)) bool EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex); +#else + bool EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex); +#endif bool DoMetalFXSpatial(GSTexture* sTex, GSTexture* dTex) override; MRCOwned> LoadShader(NSString* name); diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 481c63b670..5760e687f3 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -782,6 +782,7 @@ bool GSDeviceMTL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, con return true; }} +#if PCSX2_HAS_METALFX bool GSDeviceMTL::EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex) { @autoreleasepool { id src = static_cast(sTex)->GetTexture(); @@ -822,23 +823,39 @@ bool GSDeviceMTL::EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex) m_mfx_in_fmt = in_fmt; m_mfx_out_fmt = out_fmt; return true; }} +#else +bool GSDeviceMTL::EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex) +{ + // Statically compiled out on the iOS Simulator (PCSX2_HAS_METALFX=0). + (void)sTex; (void)dTex; + return false; +} +#endif bool GSDeviceMTL::DoMetalFXSpatial(GSTexture* sTex, GSTexture* dTex) -{ @autoreleasepool { - if (@available(macOS 13.0, iOS 16.0, *)) - { - if (!EnsureMetalFXSpatial(sTex, dTex)) - return false; +{ +#if PCSX2_HAS_METALFX + @autoreleasepool { + if (@available(macOS 13.0, iOS 16.0, *)) + { + if (!EnsureMetalFXSpatial(sTex, dTex)) + return false; - g_perfmon.Put(GSPerfMon::TextureCopies, 1); - EndRenderPass(); // MetalFX manages its own encoder; must not be inside one. - [m_mfx_spatial setColorTexture:static_cast(sTex)->GetTexture()]; - [m_mfx_spatial setOutputTexture:static_cast(dTex)->GetTexture()]; - [m_mfx_spatial encodeToCommandBuffer:GetRenderCmdBuf()]; - return true; + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + EndRenderPass(); // MetalFX manages its own encoder; must not be inside one. + [m_mfx_spatial setColorTexture:static_cast(sTex)->GetTexture()]; + [m_mfx_spatial setOutputTexture:static_cast(dTex)->GetTexture()]; + [m_mfx_spatial encodeToCommandBuffer:GetRenderCmdBuf()]; + return true; + } + return false; } +#else + // Statically compiled out on the iOS Simulator (PCSX2_HAS_METALFX=0). + (void)sTex; (void)dTex; return false; -}} +#endif +} MRCOwned> GSDeviceMTL::LoadShader(NSString* name) { @@ -1164,11 +1181,15 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) m_features.test_and_sample_depth = true; m_features.depth_feedback = getDepthFeedback(m_dev, m_features.framebuffer_fetch); m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand; - // MetalFX spatial upscaler: macOS 13+ / iOS 16+. The supportsDevice: probe - // returns NO on the iOS Simulator and on devices whose GPU lacks the hardware, - // so this is safe to run unconditionally on every Apple platform. + // MetalFX spatial upscaler: macOS 13+ / iOS 16+ device. The supportsDevice: + // probe returns NO on devices whose GPU lacks the hardware. On the iOS Simulator + // the MetalFX framework is absent at compile time (PCSX2_HAS_METALFX=0), so the + // feature is statically disabled here -- m_features.metalfx_spatial keeps its + // default false value and the upscaler UI will report unavailable. +#if PCSX2_HAS_METALFX if (@available(macOS 13.0, iOS 16.0, *)) m_features.metalfx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:m_dev.dev]; +#endif m_features.rov = m_dev.features.rov && !m_features.framebuffer_fetch; m_max_texture_size = m_dev.features.max_texsize; diff --git a/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm b/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm index f8115c5a89..a740f178f1 100644 --- a/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm +++ b/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm @@ -14,10 +14,17 @@ #define ARMSX2_HAS_SWIFTUI_HOST 0 #endif -// MetalFX spatial upscaler is iOS 16+ and weak-linked (see PCSX2 CMake). Both -// headers are pulled in here so isMetalFXSupported can probe device capability. +// MetalFX spatial upscaler is iOS 16+ device and weak-linked (see PCSX2 CMake). +// The iOS Simulator SDK does not ship the MetalFX framework, so the import is +// gated off when targeting the sim; isMetalFXSupported then returns NO without +// referencing MTLFXSpatialScalerDescriptor. #import -#import +#if !TARGET_OS_SIMULATOR + #import + #define ARMSX2_HAS_METALFX 1 +#else + #define ARMSX2_HAS_METALFX 0 +#endif #include "common/Darwin/DarwinMisc.h" #include @@ -3515,16 +3522,21 @@ static std::string ARMSX2PerGameSettingsPath(const std::string& serial, u32 crc) // Probes whether MetalFX Spatial upscaling is available on this device. This is // a standalone check that works from the main menu before any GS device exists, // so the settings UI can decide whether to show the Upscaler section at all. It -// returns NO on pre-iOS-16, the simulator, and any GPU that fails the framework -// capability probe. +// returns NO on pre-iOS-16, the simulator (statically compiled out), and any +// GPU that fails the framework capability probe. + (BOOL)isMetalFXSupported { - if (@available(iOS 16.0, *)) { - MRCOwned> device = MRCTransfer(MTLCreateSystemDefaultDevice()); - if (!device) - return NO; - return [MTLFXSpatialScalerDescriptor supportsDevice:device]; - } - return NO; +#if ARMSX2_HAS_METALFX + if (@available(iOS 16.0, *)) { + MRCOwned> device = MRCTransfer(MTLCreateSystemDefaultDevice()); + if (!device) + return NO; + return [MTLFXSpatialScalerDescriptor supportsDevice:device]; + } + return NO; +#else + // iOS Simulator build: MetalFX framework absent at compile time. + return NO; +#endif } #pragma mark - Per-game INI getter/setter diff --git a/platforms/ios/scripts/generate-ios-sim-xcode.sh b/platforms/ios/scripts/generate-ios-sim-xcode.sh new file mode 100755 index 0000000000..7550c18738 --- /dev/null +++ b/platforms/ios/scripts/generate-ios-sim-xcode.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generates a CMake Xcode project configured for the iOS Simulator (SDK +# iphonesimulator, ARMSX2_REAL_DEVICE=OFF). This is the project XcodeBuildMCP +# should target for simulator workflows (debug, test, UI automation). Device +# IPAs continue to use generate-ios-xcode.sh + build-ios-ipa.sh. +# +# The sim project disables the -weak_framework MetalFX link flag (see +# pcsx2/CMakeLists.txt) because MetalFX.framework is absent from the +# iphonesimulator SDK; all source-level MetalFX references are also +# compile-guarded by PCSX2_HAS_METALFX=0 (see GSDeviceMTL.h). + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SOURCE_DIR="$ROOT_DIR/app/src/main/cpp" +BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build-ios-sim-xcode}" +BUNDLE_ID="${BUNDLE_ID:-com.armsx2.ios}" + +if ! command -v cmake >/dev/null 2>&1; then + echo "error: cmake is required to generate the iOS Simulator Xcode project." >&2 + echo "Install CMake from https://cmake.org/download/ or through your package manager." >&2 + exit 1 +fi + +cmake -S "$SOURCE_DIR" -B "$BUILD_DIR" -G Xcode \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DARMSX2_REAL_DEVICE=OFF \ + -DARMSX2_BUNDLE_IDENTIFIER="$BUNDLE_ID" + +cat <