Fix iOS Metal startup crash

This commit is contained in:
aperezro 2026-06-08 00:18:10 -06:00
parent 42ed8f1580
commit f532a8e73b
3 changed files with 211 additions and 14 deletions

View file

@ -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,

View file

@ -60,7 +60,7 @@
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>MinimumOSVersion</key>
<string>13.0</string>
<string>16.0</string>
<key>UIFileSharingEnabled</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>

View file

@ -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<CA::MetalLayer*>(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<RenderDescriptorSet> 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();