diff --git a/CMakePresets.json b/CMakePresets.json index 1aa41db5..402787cc 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -176,6 +176,7 @@ "CMAKE_SYSTEM_NAME": "iOS", "CMAKE_OSX_SYSROOT": "iphonesimulator", "CMAKE_OSX_ARCHITECTURES": "arm64", + "CMAKE_OSX_DEPLOYMENT_TARGET": "16.0", "CMAKE_BUILD_TYPE": "Debug", "UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true, "UNLEASHED_RECOMP_HOST_TOOLS_DIR": "${sourceDir}/out/build/macos-release", @@ -207,6 +208,7 @@ "CMAKE_SYSTEM_NAME": "iOS", "CMAKE_OSX_SYSROOT": "iphoneos", "CMAKE_OSX_ARCHITECTURES": "arm64", + "CMAKE_OSX_DEPLOYMENT_TARGET": "16.0", "CMAKE_BUILD_TYPE": "Release", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true, "UNLEASHED_RECOMP_SKIP_TARGET_TOOL_EXECUTABLES": true, diff --git a/UnleashedRecomp/res/ios/Info.plist.in b/UnleashedRecomp/res/ios/Info.plist.in index bfb2b80b..cc18bda1 100644 --- a/UnleashedRecomp/res/ios/Info.plist.in +++ b/UnleashedRecomp/res/ios/Info.plist.in @@ -60,7 +60,7 @@ LSSupportsOpeningDocumentsInPlace MinimumOSVersion - 13.0 + 16.0 UIFileSharingEnabled UIApplicationSupportsIndirectInputEvents diff --git a/tools/patches/plume-ios-metal.patch b/tools/patches/plume-ios-metal.patch index b2b63edb..6ad2d0dd 100644 --- a/tools/patches/plume-ios-metal.patch +++ b/tools/patches/plume-ios-metal.patch @@ -242,7 +242,7 @@ index 64e4dc9..1d14600 100644 } } diff --git a/plume_metal.cpp b/plume_metal.cpp -index ebbbaa3..6f5a82c 100644 +index ebbbaa3..854107a 100644 --- a/plume_metal.cpp +++ b/plume_metal.cpp @@ -14,8 +14,12 @@ @@ -258,10 +258,58 @@ index ebbbaa3..6f5a82c 100644 #include "plume_metal.h" -@@ -38,6 +42,141 @@ namespace plume { +@@ -38,6 +42,186 @@ namespace plume { return (n + alignment - 1) & ~(alignment - 1); } ++ bool supportsMetalFamily(MTL::Device* device, MTL::GPUFamily family) { ++ return device != nullptr && device->supportsFamily(family); ++ } ++ ++ bool supportsBufferDeviceAddress(MTL::Device* device) { ++ return supportsMetalFamily(device, MTL::GPUFamilyMetal3); ++ } ++ ++ uint64_t getRecommendedWorkingSetSize(MTL::Device* device) { ++#if PLUME_IOS ++ // recommendedMaxWorkingSetSize is iOS 16+. The app requires Metal 3 for ++ // gpuAddress, but keep startup safe on older runtimes and SDK shims. ++ (void)device; ++ return 2ULL * 1024ULL * 1024ULL * 1024ULL; ++#else ++ return device->recommendedMaxWorkingSetSize(); ++#endif ++ } ++ ++ bool hasUnifiedMemory(MTL::Device* device) { ++#if PLUME_IOS ++ (void)device; ++ return true; ++#else ++ return device->hasUnifiedMemory(); ++#endif ++ } ++ ++ bool supportsProgrammableSamplePositions(MTL::Device* device) { ++#if PLUME_IOS ++ return supportsMetalFamily(device, MTL::GPUFamilyApple3); ++#else ++ return device->programmableSamplePositionsSupported(); ++#endif ++ } ++ ++ spirv_cross::CompilerMSL::Options::ArgumentBuffersTier getArgumentBuffersTier(MTL::Device* device) { ++#if PLUME_IOS ++ return supportsMetalFamily(device, MTL::GPUFamilyApple3) ++ ? spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier2 ++ : spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier1; ++#else ++ return device->argumentBuffersSupport() == MTL::ArgumentBuffersTier2 ++ ? spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier2 ++ : spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier1; ++#endif ++ } ++ + uint32_t getSpirvResourceArrayCount(const spirv_cross::SPIRType& type) { + uint32_t count = 1; + for (const uint32_t dimension : type.array) { @@ -339,10 +387,7 @@ index ebbbaa3..6f5a82c 100644 +#endif + options.set_msl_version(3, 0); + options.argument_buffers = true; -+ options.argument_buffers_tier = -+ device->mtl->argumentBuffersSupport() == MTL::ArgumentBuffersTier2 -+ ? spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier2 -+ : spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier1; ++ options.argument_buffers_tier = getArgumentBuffersTier(device->mtl); + options.enable_base_index_zero = true; + options.enable_decoration_binding = true; + options.force_active_argument_buffer_resources = true; @@ -400,7 +445,16 @@ index ebbbaa3..6f5a82c 100644 uint64_t createClearPipelineKey(MTL::RenderPipelineDescriptor *pipelineDesc, bool depthWriteEnabled, bool stencilWriteEnabled) { auto colorFormat = [&](uint32_t index) { if (auto colorAttachment = pipelineDesc->colorAttachments()->object(index)) { -@@ -1278,24 +1417,46 @@ namespace plume { +@@ -1133,7 +1317,7 @@ namespace plume { + } + + uint64_t MetalBuffer::getDeviceAddress() const { +- assert(device->mtl->supportsFamily(MTL::GPUFamilyMetal3) && "Device address is only supported on Metal3 devices."); ++ assert(supportsBufferDeviceAddress(device->mtl) && "Device address is only supported on Metal3 devices."); + return mtl->gpuAddress(); + } + +@@ -1278,24 +1462,46 @@ namespace plume { assert(device != nullptr); assert(data != nullptr); assert(size > 0); @@ -453,7 +507,7 @@ index ebbbaa3..6f5a82c 100644 if (debugName) { debugName->release(); } -@@ -1306,10 +1467,16 @@ namespace plume { +@@ -1306,10 +1512,16 @@ namespace plume { debugName->release(); } debugName = NS::String::string(name.c_str(), NS::UTF8StringEncoding); @@ -471,7 +525,110 @@ index ebbbaa3..6f5a82c 100644 MTL::FunctionConstantValues *values = MTL::FunctionConstantValues::alloc()->init(); if (specConstants != nullptr) { for (uint32_t i = 0; i < specConstantsCount; i++) { -@@ -3308,6 +3475,9 @@ namespace plume { +@@ -1793,6 +2005,11 @@ namespace plume { + + MetalSwapChain::MetalSwapChain(MetalCommandQueue *commandQueue, const RenderWindow renderWindow, uint32_t textureCount, const RenderFormat format) { + this->layer = static_cast(renderWindow.view); ++ if (layer == nullptr) { ++ fprintf(stderr, "Metal swapchain creation failed: SDL did not provide a CAMetalLayer.\n"); ++ return; ++ } ++ + layer->setDevice(commandQueue->device->mtl); + layer->setPixelFormat(mapPixelFormat(format)); + +@@ -1819,7 +2036,10 @@ namespace plume { + } + + bool MetalSwapChain::present(const uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, const uint32_t waitSemaphoreCount) { +- assert(layer != nullptr && "Cannot present without a valid layer."); ++ if (layer == nullptr) { ++ return false; ++ } ++ + NS::AutoreleasePool *releasePool = NS::AutoreleasePool::alloc()->init(); + + const MetalDrawable &drawable = drawables[textureIndex]; +@@ -1861,7 +2081,7 @@ namespace plume { + bool MetalSwapChain::resize() { + getWindowSize(width, height); + +- if (width == 0 || height == 0) { ++ if (layer == nullptr || width == 0 || height == 0) { + return false; + } + +@@ -1886,11 +2106,29 @@ namespace plume { + } + + void MetalSwapChain::setVsyncEnabled(const bool vsyncEnabled) { ++ if (layer == nullptr) { ++ return; ++ } ++ ++#if PLUME_IOS ++ // CAMetalLayer.displaySyncEnabled is unavailable on iOS. iOS drawables ++ // are display-synchronized by default, so keep this as a no-op. ++ (void)vsyncEnabled; ++#else + layer->setDisplaySyncEnabled(vsyncEnabled); ++#endif + } + + bool MetalSwapChain::isVsyncEnabled() const { ++ if (layer == nullptr) { ++ return false; ++ } ++ ++#if PLUME_IOS ++ return true; ++#else + return layer->displaySyncEnabled(); ++#endif + } + + uint32_t MetalSwapChain::getWidth() const { +@@ -1910,6 +2148,10 @@ namespace plume { + assert(textureIndex != nullptr); + assert(*textureIndex < MAX_DRAWABLES); + ++ if (layer == nullptr) { ++ return false; ++ } ++ + NS::AutoreleasePool *releasePool = NS::AutoreleasePool::alloc()->init(); + + // Create a command buffer just to encode the signal +@@ -1923,6 +2165,7 @@ namespace plume { + CA::MetalDrawable *nextDrawable = layer->nextDrawable(); + if (nextDrawable == nullptr) { + fprintf(stderr, "No more drawables available for rendering.\n"); ++ releasePool->release(); + return false; + } + +@@ -1954,10 +2197,20 @@ namespace plume { + } + + uint32_t MetalSwapChain::getRefreshRate() const { ++ if (windowWrapper == nullptr) { ++ return 60; ++ } ++ + return windowWrapper->getRefreshRate(); + } + + void MetalSwapChain::getWindowSize(uint32_t &dstWidth, uint32_t &dstHeight) const { ++ if (windowWrapper == nullptr) { ++ dstWidth = 0; ++ dstHeight = 0; ++ return; ++ } ++ + CocoaWindowAttributes attributes; + windowWrapper->getWindowAttributes(&attributes); + dstWidth = attributes.width; +@@ -3308,6 +3561,9 @@ namespace plume { this->renderInterface = renderInterface; // Device Selection @@ -481,7 +638,7 @@ index ebbbaa3..6f5a82c 100644 const NS::Array* devices = MTL::CopyAllDevices(); MTL::Device *preferredDevice = nullptr; for (NS::UInteger i = 0; i < devices->count(); i++) { -@@ -3320,9 +3490,18 @@ namespace plume { +@@ -3320,12 +3576,31 @@ namespace plume { } mtl = preferredDevice ? preferredDevice : MTL::CreateSystemDefaultDevice();; @@ -490,6 +647,16 @@ index ebbbaa3..6f5a82c 100644 + return; + } + ++#if PLUME_IOS ++ if (!supportsBufferDeviceAddress(mtl)) { ++ fprintf(stderr, "Metal device '%s' does not support Metal 3 buffer GPU addresses required by Unleashed Recompiled.\n", mtl->name()->utf8String()); ++ mtl->release(); ++ mtl = nullptr; ++ return; ++ } ++#endif ++ ++ const uint64_t workingSetSize = getRecommendedWorkingSetSize(mtl); const std::string deviceName(mtl->name()->utf8String()); description.name = deviceName; +#if PLUME_IOS @@ -499,8 +666,36 @@ index ebbbaa3..6f5a82c 100644 +#endif description.driverVersion = 1; // Unavailable description.vendor = mtl->supportsFamily(MTL::GPUFamilyApple1) ? RenderDeviceVendor::APPLE : getRenderDeviceVendor(mtl->registryID()); - description.dedicatedVideoMemory = mtl->recommendedMaxWorkingSetSize(); -@@ -3357,17 +3536,19 @@ namespace plume { +- description.dedicatedVideoMemory = mtl->recommendedMaxWorkingSetSize(); ++ description.dedicatedVideoMemory = workingSetSize; + + // Setup blit, clear and resolve shaders / pipelines + createClearShaderLibrary(); +@@ -3337,7 +3612,7 @@ namespace plume { + // TODO: Support Raytracing. + // capabilities.raytracing = mtl->supportsRaytracing(); + capabilities.maxTextureSize = mtl->supportsFamily(MTL::GPUFamilyApple3) ? 16384 : 8192; +- capabilities.sampleLocations = mtl->programmableSamplePositionsSupported(); ++ capabilities.sampleLocations = supportsProgrammableSamplePositions(mtl); + capabilities.resolveModes = false; + #if PLUME_IOS + capabilities.descriptorIndexing = mtl->supportsFamily(MTL::GPUFamilyApple3); +@@ -3345,11 +3620,11 @@ namespace plume { + capabilities.descriptorIndexing = true; + #endif + capabilities.scalarBlockLayout = true; +- capabilities.bufferDeviceAddress = mtl->supportsFamily(MTL::GPUFamilyApple3); ++ capabilities.bufferDeviceAddress = supportsBufferDeviceAddress(mtl); + capabilities.presentWait = false; +- capabilities.preferHDR = mtl->recommendedMaxWorkingSetSize() > (512 * 1024 * 1024); ++ capabilities.preferHDR = workingSetSize > (512 * 1024 * 1024); + capabilities.dynamicDepthBias = true; +- capabilities.uma = mtl->hasUnifiedMemory(); ++ capabilities.uma = hasUnifiedMemory(mtl); + capabilities.gpuUploadHeap = capabilities.uma; + capabilities.queryPools = false; + +@@ -3357,17 +3632,19 @@ namespace plume { } MetalDevice::~MetalDevice() { @@ -526,7 +721,7 @@ index ebbbaa3..6f5a82c 100644 } std::unique_ptr MetalDevice::createDescriptorSet(const RenderDescriptorSetDesc &desc) { -@@ -3635,16 +3816,24 @@ namespace plume { +@@ -3635,16 +3912,24 @@ namespace plume { MetalInterface::MetalInterface() { NS::AutoreleasePool *releasePool = NS::AutoreleasePool::alloc()->init();