From c7fe1c11e7baaddde5cc0f00b653e3416db0a6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20P=C3=A9rez?= Date: Sat, 16 May 2026 09:13:26 -0500 Subject: [PATCH 1/2] Add initial iOS port scaffolding --- CMakePresets.json | 59 ++++++++++++++++++++++++++++++++++ UnleashedRecomp/CMakeLists.txt | 27 +++++++++++++--- thirdparty/CMakeLists.txt | 12 +++++-- thirdparty/nfd_ios_stub.c | 42 ++++++++++++++++++++++++ tools/package_ios_ipa.sh | 53 ++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 thirdparty/nfd_ios_stub.c create mode 100755 tools/package_ios_ipa.sh diff --git a/CMakePresets.json b/CMakePresets.json index b64ca36a8..cbd2260af 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -165,6 +165,65 @@ "CMAKE_BUILD_TYPE": "Release", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true } + }, + { + "name": "ios-simulator-debug", + "displayName": "iOS Simulator Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "iOS", + "CMAKE_OSX_SYSROOT": "iphonesimulator", + "CMAKE_OSX_ARCHITECTURES": "arm64", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": { + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "type": "FILEPATH" + }, + "VCPKG_TARGET_TRIPLET": { + "value": "arm64-ios-simulator", + "type": "STRING" + } + }, + "environment": { + "VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "ios-device-release", + "displayName": "iOS Device Release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "iOS", + "CMAKE_OSX_SYSROOT": "iphoneos", + "CMAKE_OSX_ARCHITECTURES": "arm64", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true, + "CMAKE_TOOLCHAIN_FILE": { + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "type": "FILEPATH" + }, + "VCPKG_TARGET_TRIPLET": { + "value": "arm64-ios", + "type": "STRING" + } + }, + "environment": { + "VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } } ] } diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 01e040ab8..d22da7b9a 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -97,9 +97,9 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") "os/linux/user_linux.cpp" "os/linux/version_linux.cpp" ) -elseif (APPLE) - set(UNLEASHED_RECOMP_OS_CXX_SOURCES - "os/macos/logger_macos.cpp" +elseif (APPLE) + set(UNLEASHED_RECOMP_OS_CXX_SOURCES + "os/macos/logger_macos.cpp" "os/macos/media_macos.cpp" "os/macos/process_macos.cpp" "os/macos/user_macos.cpp" @@ -292,7 +292,26 @@ if (WIN32) if (${CMAKE_BUILD_TYPE} MATCHES "Release") target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") endif() -elseif (APPLE) +elseif (CMAKE_SYSTEM_NAME STREQUAL "iOS") + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + OUTPUT_VAR IOS_BUNDLE_VERSION + ) + + add_executable(UnleashedRecomp MACOSX_BUNDLE + ${UNLEASHED_RECOMP_CXX_SOURCES} + ) + set_target_properties(UnleashedRecomp PROPERTIES + OUTPUT_NAME "Unleashed Recompiled" + MACOSX_BUNDLE_GUI_IDENTIFIER hedge-dev.UnleashedRecomp + MACOSX_BUNDLE_BUNDLE_NAME "Unleashed Recompiled" + MACOSX_BUNDLE_BUNDLE_VERSION ${IOS_BUNDLE_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${IOS_BUNDLE_VERSION} + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "hedge-dev.UnleashedRecomp" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" + XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" + ) +elseif (APPLE) # Create version number for app bundle. CreateVersionString( VERSION_TXT ${VERSION_TXT} diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index d5c085818..d5e749a9a 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -15,7 +15,7 @@ set(SDL2MIXER_OPUS OFF) set(SDL2MIXER_VORBIS "VORBISFILE") set(SDL2MIXER_WAVPACK OFF) -if (CMAKE_SYSTEM_NAME MATCHES "Linux") +if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "iOS") set(SDL_VULKAN_ENABLED ON CACHE BOOL "") endif() @@ -24,12 +24,18 @@ if (WIN32) endif() add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/msdf-atlas-gen") -add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended") +if (CMAKE_SYSTEM_NAME STREQUAL "iOS") + add_library(nfd STATIC "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nfd_ios_stub.c") + add_library(nfd::nfd ALIAS nfd) + target_include_directories(nfd PUBLIC "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended/src/include") +else() + add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended") +endif() add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/o1heap") add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/SDL") add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/SDL_mixer") add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/plume") -if (APPLE) +if (APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/MoltenVK") endif() diff --git a/thirdparty/nfd_ios_stub.c b/thirdparty/nfd_ios_stub.c new file mode 100644 index 000000000..05476b3e6 --- /dev/null +++ b/thirdparty/nfd_ios_stub.c @@ -0,0 +1,42 @@ +#include +#include + +static const char* g_nfd_ios_error = "Native file dialogs are not implemented for iOS yet."; + +nfdresult_t NFD_Init(void) { return NFD_OKAY; } +void NFD_Quit(void) {} +const char* NFD_GetError(void) { return g_nfd_ios_error; } + +void NFD_FreePathN(nfdnchar_t* filePath) { free(filePath); } +void NFD_FreePathU8(nfdu8char_t* filePath) { free(filePath); } + +nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdopendialognargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdopendialogu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { (void)outPaths; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath) { (void)outPaths; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogMultipleN_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdopendialognargs_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; } +nfdresult_t NFD_OpenDialogMultipleU8_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdopendialogu8args_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; } +nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath, const nfdnchar_t* defaultName) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; (void)defaultName; return NFD_ERROR; } +nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath, const nfdu8char_t* defaultName) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; (void)defaultName; return NFD_ERROR; } +nfdresult_t NFD_SaveDialogN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdsavedialognargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_SaveDialogU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdsavedialogu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { (void)outPath; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath) { (void)outPath; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdpickfoldernargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdpickfolderu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; } +nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) { (void)outPaths; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath) { (void)outPaths; (void)defaultPath; return NFD_ERROR; } +nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdpickfoldernargs_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; } +nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdpickfolderu8args_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; } +nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) { (void)pathSet; if (count) *count = 0; return NFD_OKAY; } +nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, nfdpathsetsize_t index, nfdnchar_t** outPath) { (void)pathSet; (void)index; (void)outPath; return NFD_ERROR; } +nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet, nfdpathsetsize_t index, nfdu8char_t** outPath) { (void)pathSet; (void)index; (void)outPath; return NFD_ERROR; } +void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) { free((void*)filePath); } +void NFD_PathSet_FreePathU8(const nfdu8char_t* filePath) { free((void*)filePath); } +void NFD_PathSet_Free(const nfdpathset_t* pathSet) { (void)pathSet; } +void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) { (void)enumerator; } +nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t** outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; } +nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; } +nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; } diff --git a/tools/package_ios_ipa.sh b/tools/package_ios_ipa.sh new file mode 100755 index 000000000..becb4455b --- /dev/null +++ b/tools/package_ios_ipa.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD_DIR="$ROOT/out/build/ios-device-release" +IPA_DIR="$ROOT/out/ipa" +PAYLOAD_DIR="$IPA_DIR/Payload" +APP_PATH="$BUILD_DIR/Unleashed Recompiled.app" +IPA_PATH="$IPA_DIR/UnleashedRecompiled.ipa" + +missing=0 +for file in \ + "$ROOT/UnleashedRecompLib/private/default.xex" \ + "$ROOT/UnleashedRecompLib/private/default.xexp" \ + "$ROOT/UnleashedRecompLib/private/shader.ar"; do + if [[ ! -f "$file" ]]; then + printf 'Missing required file: %s\n' "$file" >&2 + missing=1 + fi +done + +if [[ "$missing" -ne 0 ]]; then + exit 1 +fi + +cmake --preset ios-device-release +cmake --build "$BUILD_DIR" --target UnleashedRecomp -j "${JOBS:-8}" + +if [[ ! -d "$APP_PATH" ]]; then + printf 'Expected app bundle was not produced: %s\n' "$APP_PATH" >&2 + exit 1 +fi + +rm -rf "$IPA_DIR" +mkdir -p "$PAYLOAD_DIR" +cp -R "$APP_PATH" "$PAYLOAD_DIR/" + +if [[ -n "${MOBILEPROVISION:-}" ]]; then + cp "$MOBILEPROVISION" "$PAYLOAD_DIR/Unleashed Recompiled.app/embedded.mobileprovision" +fi + +if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then + codesign --force --sign "$CODESIGN_IDENTITY" \ + ${ENTITLEMENTS:+--entitlements "$ENTITLEMENTS"} \ + "$PAYLOAD_DIR/Unleashed Recompiled.app" +fi + +( + cd "$IPA_DIR" + zip -qry "$IPA_PATH" Payload +) + +printf '%s\n' "$IPA_PATH" From 4f9e6cf2ffb748eb1e7d9a32e7dcd6b673280016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20P=C3=A9rez?= Date: Mon, 18 May 2026 18:02:53 -0500 Subject: [PATCH 2/2] Make iOS packaging reproducible --- tools/apply_ios_submodule_patches.sh | 23 ++++++++ tools/package_ios_ipa.sh | 21 ++++++- tools/patches/plume-ios-sdl-vulkan.patch | 72 ++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100755 tools/apply_ios_submodule_patches.sh create mode 100644 tools/patches/plume-ios-sdl-vulkan.patch diff --git a/tools/apply_ios_submodule_patches.sh b/tools/apply_ios_submodule_patches.sh new file mode 100755 index 000000000..e8d96633f --- /dev/null +++ b/tools/apply_ios_submodule_patches.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PLUME_DIR="$ROOT/thirdparty/plume" +PATCH_FILE="$ROOT/tools/patches/plume-ios-sdl-vulkan.patch" + +if [[ ! -d "$PLUME_DIR/.git" && ! -f "$PLUME_DIR/.git" ]]; then + printf 'Missing Plume submodule. Run: git submodule update --init --recursive\n' >&2 + exit 1 +fi + +if git -C "$PLUME_DIR" apply --check "$PATCH_FILE" >/dev/null 2>&1; then + git -C "$PLUME_DIR" apply "$PATCH_FILE" + printf 'Applied Plume iOS patch.\n' +else + if git -C "$PLUME_DIR" apply --reverse --check "$PATCH_FILE" >/dev/null 2>&1; then + printf 'Plume iOS patch already applied.\n' + else + printf 'Plume iOS patch cannot be applied cleanly. Check thirdparty/plume for local changes.\n' >&2 + exit 1 + fi +fi diff --git a/tools/package_ios_ipa.sh b/tools/package_ios_ipa.sh index becb4455b..aab2c2224 100755 --- a/tools/package_ios_ipa.sh +++ b/tools/package_ios_ipa.sh @@ -7,6 +7,9 @@ IPA_DIR="$ROOT/out/ipa" PAYLOAD_DIR="$IPA_DIR/Payload" APP_PATH="$BUILD_DIR/Unleashed Recompiled.app" IPA_PATH="$IPA_DIR/UnleashedRecompiled.ipa" +DEFAULT_SIGNING_IDENTITY="Apple Development: aroblesalago@gmail.com (MK28YRUCCG)" + +"$ROOT/tools/apply_ios_submodule_patches.sh" missing=0 for file in \ @@ -20,6 +23,7 @@ for file in \ done if [[ "$missing" -ne 0 ]]; then + printf '\nAdd these files from your own compatible Sonic Unleashed Xbox 360 dump before building an IPA.\n' >&2 exit 1 fi @@ -35,11 +39,24 @@ rm -rf "$IPA_DIR" mkdir -p "$PAYLOAD_DIR" cp -R "$APP_PATH" "$PAYLOAD_DIR/" -if [[ -n "${MOBILEPROVISION:-}" ]]; then - cp "$MOBILEPROVISION" "$PAYLOAD_DIR/Unleashed Recompiled.app/embedded.mobileprovision" +if [[ -z "${CODESIGN_IDENTITY:-}" ]] && security find-identity -v -p codesigning | grep -q "$DEFAULT_SIGNING_IDENTITY"; then + CODESIGN_IDENTITY="$DEFAULT_SIGNING_IDENTITY" +fi + +if [[ -n "${MOBILEPROVISION:-}" && ! -f "${MOBILEPROVISION:-}" ]]; then + printf 'Provisioning profile not found: %s\n' "$MOBILEPROVISION" >&2 + exit 1 fi if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then + if [[ -z "${MOBILEPROVISION:-}" ]]; then + printf 'CODESIGN_IDENTITY is set, but MOBILEPROVISION is not. Producing an unsigned IPA for sideloading tools that sign on import.\n' >&2 + else + cp "$MOBILEPROVISION" "$PAYLOAD_DIR/Unleashed Recompiled.app/embedded.mobileprovision" + fi +fi + +if [[ -n "${CODESIGN_IDENTITY:-}" && -n "${MOBILEPROVISION:-}" ]]; then codesign --force --sign "$CODESIGN_IDENTITY" \ ${ENTITLEMENTS:+--entitlements "$ENTITLEMENTS"} \ "$PAYLOAD_DIR/Unleashed Recompiled.app" diff --git a/tools/patches/plume-ios-sdl-vulkan.patch b/tools/patches/plume-ios-sdl-vulkan.patch new file mode 100644 index 000000000..e2a6e17e0 --- /dev/null +++ b/tools/patches/plume-ios-sdl-vulkan.patch @@ -0,0 +1,72 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6a7645a..bf336b9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -8,10 +8,16 @@ if(APPLE) + endif() + + string(COMPARE EQUAL ${CMAKE_SYSTEM_NAME} "Linux" IS_LINUX) ++string(COMPARE EQUAL ${CMAKE_SYSTEM_NAME} "iOS" IS_IOS) ++if(IS_LINUX OR IS_IOS) ++ set(IS_SDL_VULKAN_PLATFORM ON) ++else() ++ set(IS_SDL_VULKAN_PLATFORM OFF) ++endif() + + # Project options + include(CMakeDependentOption) +-cmake_dependent_option(SDL_VULKAN_ENABLED "Enable SDL Vulkan integration" OFF IS_LINUX OFF) ++cmake_dependent_option(SDL_VULKAN_ENABLED "Enable SDL Vulkan integration" OFF IS_SDL_VULKAN_PLATFORM OFF) + cmake_dependent_option(D3D12_AGILITY_SDK_ENABLED "Enable D3D12 Agility SDK" OFF WIN32 OFF) + option(PLUME_BUILD_EXAMPLES "Build example applications" OFF) + +diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp +index 9103ca8..02c3591 100644 +--- a/plume_vulkan.cpp ++++ b/plume_vulkan.cpp +@@ -2112,6 +2112,12 @@ namespace plume { + fprintf(stderr, "vkCreateXlibSurfaceKHR failed with error code 0x%X.\n", res); + return; + } ++# elif defined(__APPLE__) && defined(SDL_VULKAN_ENABLED) ++ VulkanInterface *renderInterface = commandQueue->device->renderInterface; ++ if (!SDL_Vulkan_CreateSurface(renderWindow, renderInterface->instance, &surface)) { ++ fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s.\n", SDL_GetError()); ++ return; ++ } + # elif defined(__APPLE__) + assert(renderWindow.window != 0); + assert(renderWindow.view != 0); +@@ -2443,7 +2449,7 @@ namespace plume { + // The attributes width and height members do not include the border. + dstWidth = attributes.width; + dstHeight = attributes.height; +-# elif defined(__APPLE__) ++# elif defined(__APPLE__) && !defined(SDL_VULKAN_ENABLED) + CocoaWindowAttributes attributes; + windowWrapper->getWindowAttributes(&attributes); + dstWidth = attributes.width; +diff --git a/plume_vulkan.h b/plume_vulkan.h +index 73022bb..9d89adf 100644 +--- a/plume_vulkan.h ++++ b/plume_vulkan.h +@@ -22,8 +22,10 @@ + #define VK_USE_PLATFORM_XLIB_KHR + #elif defined(__APPLE__) + #define VK_USE_PLATFORM_METAL_EXT ++#ifndef SDL_VULKAN_ENABLED + #include "plume_apple.h" + #endif ++#endif + + // For VK_KHR_portability_subset + #define VK_ENABLE_BETA_EXTENSIONS +@@ -226,7 +228,7 @@ namespace plume { + VulkanCommandQueue *commandQueue = nullptr; + VkSurfaceKHR surface = VK_NULL_HANDLE; + RenderWindow renderWindow = {}; +-#if defined(__APPLE__) ++#if defined(__APPLE__) && !defined(SDL_VULKAN_ENABLED) + std::unique_ptr windowWrapper; + #endif + uint32_t textureCount = 0;