2
0
Эх сурвалжийг харах

Introduce 'drivers/apple_embedded' abstract platform for code reuse

Ricardo Sanchez-Saez 4 сар өмнө
parent
commit
457299449d
84 өөрчлөгдсөн 6102 нэмэгдсэн , 3459 устгасан
  1. 5 1
      SConstruct
  2. 1 1
      core/input/input_builders.py
  3. 13 0
      doc/classes/EditorExportPlatformAppleEmbedded.xml
  4. 1 1
      doc/classes/EditorExportPlatformPC.xml
  5. 63 12
      doc/classes/EditorExportPlugin.xml
  6. 3 2
      drivers/SCsub
  7. 16 0
      drivers/apple_embedded/SCsub
  8. 4 11
      drivers/apple_embedded/app_delegate_service.h
  9. 19 20
      drivers/apple_embedded/app_delegate_service.mm
  10. 59 0
      drivers/apple_embedded/apple_embedded.h
  11. 21 21
      drivers/apple_embedded/apple_embedded.mm
  12. 9 20
      drivers/apple_embedded/display_layer_apple_embedded.h
  13. 233 0
      drivers/apple_embedded/display_server_apple_embedded.h
  14. 791 0
      drivers/apple_embedded/display_server_apple_embedded.mm
  15. 4 4
      drivers/apple_embedded/godot_app_delegate.h
  16. 36 40
      drivers/apple_embedded/godot_app_delegate.mm
  17. 19 11
      drivers/apple_embedded/godot_view_apple_embedded.h
  18. 43 64
      drivers/apple_embedded/godot_view_apple_embedded.mm
  19. 2 2
      drivers/apple_embedded/godot_view_renderer.h
  20. 7 7
      drivers/apple_embedded/godot_view_renderer.mm
  21. 3 3
      drivers/apple_embedded/key_mapping_apple_embedded.h
  22. 5 5
      drivers/apple_embedded/key_mapping_apple_embedded.mm
  23. 1 1
      drivers/apple_embedded/keyboard_input_view.h
  24. 8 8
      drivers/apple_embedded/keyboard_input_view.mm
  25. 35 0
      drivers/apple_embedded/main_utilities.h
  26. 26 50
      drivers/apple_embedded/main_utilities.mm
  27. 137 0
      drivers/apple_embedded/os_apple_embedded.h
  28. 717 0
      drivers/apple_embedded/os_apple_embedded.mm
  29. 44 0
      drivers/apple_embedded/platform_config.h
  30. 4 4
      drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.h
  31. 6 6
      drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.mm
  32. 4 4
      drivers/apple_embedded/terminal_logger_apple_embedded.h
  33. 5 5
      drivers/apple_embedded/terminal_logger_apple_embedded.mm
  34. 2 2
      drivers/apple_embedded/tts_apple_embedded.h
  35. 3 3
      drivers/apple_embedded/tts_apple_embedded.mm
  36. 5 6
      drivers/apple_embedded/view_controller.h
  37. 31 31
      drivers/apple_embedded/view_controller.mm
  38. 0 1
      drivers/coremidi/midi_driver_coremidi.mm
  39. 3 0
      drivers/metal/SCsub
  40. 2 2
      drivers/metal/rendering_device_driver_metal.mm
  41. 2750 0
      editor/export/editor_export_platform_apple_embedded.cpp
  42. 316 0
      editor/export/editor_export_platform_apple_embedded.h
  43. 49 37
      editor/export/editor_export_plugin.cpp
  44. 27 27
      editor/export/editor_export_plugin.h
  45. 44 44
      editor/export/plugin_config_apple_embedded.cpp
  46. 6 6
      editor/export/plugin_config_apple_embedded.h
  47. 5 5
      editor/plugins/gdextension_export_plugin.h
  48. 1 1
      editor/plugins/lightmap_gi_editor_plugin.cpp
  49. 2 0
      editor/register_editor_types.cpp
  50. 1 1
      main/main.cpp
  51. 17 1
      methods.py
  52. 0 4
      misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
  53. 2 2
      modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
  54. 1 1
      modules/mono/editor/hostfxr_resolver.cpp
  55. 3 3
      modules/mono/mono_gd/gd_mono.cpp
  56. 1 1
      platform/android/export/export_plugin.cpp
  57. 4 0
      platform/ios/README.md
  58. 10 22
      platform/ios/SCsub
  59. 2 2
      platform/ios/api/api.cpp
  60. 2 2
      platform/ios/api/api.h
  61. 16 18
      platform/ios/detect.py
  62. 1 1
      platform/ios/device_metrics.h
  63. 2 2
      platform/ios/device_metrics.mm
  64. 7 14
      platform/ios/display_layer_ios.h
  65. 4 4
      platform/ios/display_layer_ios.mm
  66. 3 187
      platform/ios/display_server_ios.h
  67. 5 744
      platform/ios/display_server_ios.mm
  68. 10 10
      platform/ios/doc_classes/EditorExportPlatformIOS.xml
  69. 97 848
      platform/ios/export/export_plugin.cpp
  70. 10 247
      platform/ios/export/export_plugin.h
  71. 37 0
      platform/ios/godot_view_ios.h
  72. 87 0
      platform/ios/godot_view_ios.mm
  73. 3 25
      platform/ios/ios.h
  74. 89 0
      platform/ios/main_ios.mm
  75. 2 93
      platform/ios/os_ios.h
  76. 4 675
      platform/ios/os_ios.mm
  77. 1 12
      platform/ios/platform_config.h
  78. 2 62
      platform/ios/platform_ios_builders.py
  79. 2 0
      platform/macos/README.md
  80. 77 0
      platform_methods.py
  81. 1 1
      scene/3d/lightmap_gi.cpp
  82. 7 7
      servers/rendering/renderer_rd/environment/fog.cpp
  83. 1 1
      servers/rendering/renderer_rd/shader_rd.cpp
  84. 1 1
      servers/rendering/renderer_rd/storage_rd/utilities.cpp

+ 5 - 1
SConstruct

@@ -647,7 +647,11 @@ if env["scu_build"]:
 # are actually handled to change compile options, etc.
 detect.configure(env)
 
-print(f'Building for platform "{env["platform"]}", architecture "{env["arch"]}", target "{env["target"]}".')
+platform_string = env["platform"]
+if env.get("simulator"):
+    platform_string += " (simulator)"
+print(f'Building for platform "{platform_string}", architecture "{env["arch"]}", target "{env["target"]}".')
+
 if env.dev_build:
     print_info("Developer build, with debug optimization level and debug symbols (unless overridden).")
 

+ 1 - 1
core/input/input_builders.py

@@ -48,7 +48,7 @@ def make_default_controller_mappings(target, source, env):
             "Windows": "WINDOWS",
             "Mac OS X": "MACOS",
             "Android": "ANDROID",
-            "iOS": "IOS",
+            "iOS": "APPLE_EMBEDDED",
             "Web": "WEB",
         }
 

+ 13 - 0
doc/classes/EditorExportPlatformAppleEmbedded.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformAppleEmbedded" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+	<brief_description>
+		Base class for the Apple embedded platform exporters (iOS).
+	</brief_description>
+	<description>
+		The base class for Apple embedded platform exporters. These include iOS, but not macOS. See the classes inheriting from this one for more details.
+	</description>
+	<tutorials>
+		<link title="Exporting for iOS">$DOCS_URL/tutorials/export/exporting_for_ios.html</link>
+		<link title="iOS plugins documentation index">$DOCS_URL/tutorials/platform/ios/index.html</link>
+	</tutorials>
+</class>

+ 1 - 1
doc/classes/EditorExportPlatformPC.xml

@@ -4,7 +4,7 @@
 		Base class for the desktop platform exporter (Windows and Linux/BSD).
 	</brief_description>
 	<description>
-		The base class for the desktop platform exporters. These include Windows and Linux/BSD, but not macOS. See the classes inheriting this one for more details.
+		The base class for the desktop platform exporters. These include Windows and Linux/BSD, but not macOS. See the classes inheriting from this one for more details.
 	</description>
 	<tutorials>
 		<link title="Exporting for Windows">$DOCS_URL/tutorials/export/exporting_for_windows.html</link>

+ 63 - 12
doc/classes/EditorExportPlugin.xml

@@ -251,6 +251,57 @@
 				If no modifications are needed, then an empty [PackedByteArray] should be returned.
 			</description>
 		</method>
+		<method name="add_apple_embedded_platform_bundle_file">
+			<return type="void" />
+			<param index="0" name="path" type="String" />
+			<description>
+				Adds an Apple embedded platform bundle file from the given [param path] to the exported project.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_cpp_code">
+			<return type="void" />
+			<param index="0" name="code" type="String" />
+			<description>
+				Adds C++ code to the Apple embedded platform export. The final code is created from the code appended by each active export plugin.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_embedded_framework">
+			<return type="void" />
+			<param index="0" name="path" type="String" />
+			<description>
+				Adds a dynamic library (*.dylib, *.framework) to the Linking Phase in the Apple embedded platform's Xcode project and embeds it into the resulting binary.
+				[b]Note:[/b] For static libraries (*.a), this works in the same way as [method add_apple_embedded_platform_framework].
+				[b]Note:[/b] This method should not be used for System libraries as they are already present on the device.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_framework">
+			<return type="void" />
+			<param index="0" name="path" type="String" />
+			<description>
+				Adds a static library (*.a) or a dynamic library (*.dylib, *.framework) to the Linking Phase to the Apple embedded platform's Xcode project.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_linker_flags">
+			<return type="void" />
+			<param index="0" name="flags" type="String" />
+			<description>
+				Adds linker flags for the Apple embedded platform export.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_plist_content">
+			<return type="void" />
+			<param index="0" name="plist_content" type="String" />
+			<description>
+				Adds additional fields to the Apple embedded platform's project Info.plist file.
+			</description>
+		</method>
+		<method name="add_apple_embedded_platform_project_static_lib">
+			<return type="void" />
+			<param index="0" name="path" type="String" />
+			<description>
+				Adds a static library from the given [param path] to the Apple embedded platform project.
+			</description>
+		</method>
 		<method name="add_file">
 			<return type="void" />
 			<param index="0" name="path" type="String" />
@@ -262,55 +313,55 @@
 				[param file] will not be imported, so consider using [method _customize_resource] to remap imported resources.
 			</description>
 		</method>
-		<method name="add_ios_bundle_file">
+		<method name="add_ios_bundle_file" deprecated="Use [method add_apple_embedded_platform_bundle_file] instead.">
 			<return type="void" />
 			<param index="0" name="path" type="String" />
 			<description>
 				Adds an iOS bundle file from the given [param path] to the exported project.
 			</description>
 		</method>
-		<method name="add_ios_cpp_code">
+		<method name="add_ios_cpp_code" deprecated="Use [method add_apple_embedded_platform_cpp_code] instead.">
 			<return type="void" />
 			<param index="0" name="code" type="String" />
 			<description>
-				Adds a C++ code to the iOS export. The final code is created from the code appended by each active export plugin.
+				Adds C++ code to the iOS export. The final code is created from the code appended by each active export plugin.
 			</description>
 		</method>
-		<method name="add_ios_embedded_framework">
+		<method name="add_ios_embedded_framework" deprecated="Use [method add_apple_embedded_platform_embedded_framework] instead.">
 			<return type="void" />
 			<param index="0" name="path" type="String" />
 			<description>
 				Adds a dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project and embeds it into resulting binary.
-				[b]Note:[/b] For static libraries (*.a) works in same way as [method add_ios_framework].
+				[b]Note:[/b] For static libraries (*.a), this works the in same way as [method add_apple_embedded_platform_framework].
 				[b]Note:[/b] This method should not be used for System libraries as they are already present on the device.
 			</description>
 		</method>
-		<method name="add_ios_framework">
+		<method name="add_ios_framework" deprecated="Use [method add_apple_embedded_platform_framework] instead.">
 			<return type="void" />
 			<param index="0" name="path" type="String" />
 			<description>
-				Adds a static library (*.a) or dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project.
+				Adds a static library (*.a) or a dynamic library (*.dylib, *.framework) to the Linking Phase to the iOS Xcode project.
 			</description>
 		</method>
-		<method name="add_ios_linker_flags">
+		<method name="add_ios_linker_flags" deprecated="Use [method add_apple_embedded_platform_linker_flags] instead.">
 			<return type="void" />
 			<param index="0" name="flags" type="String" />
 			<description>
 				Adds linker flags for the iOS export.
 			</description>
 		</method>
-		<method name="add_ios_plist_content">
+		<method name="add_ios_plist_content" deprecated="Use [method add_apple_embedded_platform_plist_content] instead.">
 			<return type="void" />
 			<param index="0" name="plist_content" type="String" />
 			<description>
-				Adds content for iOS Property List files.
+				Adds additional fields to the iOS project Info.plist file.
 			</description>
 		</method>
-		<method name="add_ios_project_static_lib">
+		<method name="add_ios_project_static_lib" deprecated="Use [method add_apple_embedded_platform_project_static_lib] instead.">
 			<return type="void" />
 			<param index="0" name="path" type="String" />
 			<description>
-				Adds a static lib from the given [param path] to the iOS project.
+				Adds a static library from the given [param path] to the iOS project.
 			</description>
 		</method>
 		<method name="add_macos_plugin_file">

+ 3 - 2
drivers/SCsub

@@ -14,8 +14,6 @@ SConscript("windows/SCsub")
 
 # Sounds drivers
 SConscript("alsa/SCsub")
-if env["platform"] == "ios" or env["platform"] == "macos":
-    SConscript("coreaudio/SCsub")
 SConscript("pulseaudio/SCsub")
 if env["platform"] == "windows":
     SConscript("wasapi/SCsub")
@@ -30,6 +28,9 @@ if env["xaudio2"]:
 # Shared Apple platform drivers
 if env["platform"] in ["macos", "ios"]:
     SConscript("apple/SCsub")
+    SConscript("coreaudio/SCsub")
+if env["platform"] in ["ios"]:
+    SConscript("apple_embedded/SCsub")
 
 # Accessibility
 if env["accesskit"] and env["platform"] in ["macos", "windows", "linuxbsd"]:

+ 16 - 0
drivers/apple_embedded/SCsub

@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+from misc.utility.scons_hints import *
+
+Import("env")
+
+env_apple_embedded = env.Clone()
+
+# Enable module support
+env_apple_embedded.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
+# Use bundled Vulkan headers
+vulkan_dir = "#thirdparty/vulkan"
+env_apple_embedded.Prepend(CPPPATH=[vulkan_dir, vulkan_dir + "/include"])
+
+# Driver source files
+env_apple_embedded.add_source_files(env.drivers_sources, "*.mm")

+ 4 - 11
platform/ios/app_delegate.h → drivers/apple_embedded/app_delegate_service.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  app_delegate.h                                                        */
+/*  app_delegate_service.h                                                */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -32,18 +32,11 @@
 
 #import <UIKit/UIKit.h>
 
-@class ViewController;
+@class GDTViewController;
 
-// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented again,
-// so it can't be done with compilation time branching.
-//#if defined(GLES3_ENABLED)
-//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
-//#endif
-//#if defined(VULKAN_ENABLED)
-@interface AppDelegate : NSObject <UIApplicationDelegate>
-//#endif
+@interface GDTAppDelegateService : NSObject <UIApplicationDelegate>
 
 @property(strong, nonatomic) UIWindow *window;
-@property(strong, class, readonly, nonatomic) ViewController *viewController;
+@property(strong, class, readonly, nonatomic) GDTViewController *viewController;
 
 @end

+ 19 - 20
platform/ios/app_delegate.mm → drivers/apple_embedded/app_delegate_service.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  app_delegate.mm                                                       */
+/*  app_delegate_service.mm                                               */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,10 +28,10 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "app_delegate.h"
+#import "app_delegate_service.h"
 
-#import "godot_view.h"
-#import "os_ios.h"
+#import "godot_view_apple_embedded.h"
+#import "os_apple_embedded.h"
 #import "view_controller.h"
 
 #include "core/config/project_settings.h"
@@ -46,10 +46,10 @@
 extern int gargc;
 extern char **gargv;
 
-extern int ios_main(int, char **);
-extern void ios_finish();
+extern int apple_embedded_main(int, char **);
+extern void apple_embedded_finish();
 
-@implementation AppDelegate
+@implementation GDTAppDelegateService
 
 enum {
 	SESSION_CATEGORY_AMBIENT,
@@ -60,9 +60,9 @@ enum {
 	SESSION_CATEGORY_SOLO_AMBIENT
 };
 
-static ViewController *mainViewController = nil;
+static GDTViewController *mainViewController = nil;
 
-+ (ViewController *)viewController {
++ (GDTViewController *)viewController {
 	return mainViewController;
 }
 
@@ -71,12 +71,11 @@ static ViewController *mainViewController = nil;
 	// TODO: logo screen is not displayed while shaders are compiling
 	// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
 
-	CGRect windowBounds = [[UIScreen mainScreen] bounds];
-
 	// Create a full-screen window
+	CGRect windowBounds = [[UIScreen mainScreen] bounds];
 	self.window = [[UIWindow alloc] initWithFrame:windowBounds];
 
-	int err = ios_main(gargc, gargv);
+	int err = apple_embedded_main(gargc, gargv);
 
 	if (err != 0) {
 		// bail, things did not go very well for us, should probably output a message on screen with our error code...
@@ -84,7 +83,7 @@ static ViewController *mainViewController = nil;
 		return NO;
 	}
 
-	ViewController *viewController = [[ViewController alloc] init];
+	GDTViewController *viewController = [[GDTViewController alloc] init];
 	viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
 	viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
 
@@ -135,10 +134,10 @@ static ViewController *mainViewController = nil;
 	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
 		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
 			NSLog(@"Audio interruption began");
-			OS_IOS::get_singleton()->on_focus_out();
+			OS_AppleEmbedded::get_singleton()->on_focus_out();
 		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
 			NSLog(@"Audio interruption ended");
-			OS_IOS::get_singleton()->on_focus_in();
+			OS_AppleEmbedded::get_singleton()->on_focus_in();
 		}
 	}
 }
@@ -150,7 +149,7 @@ static ViewController *mainViewController = nil;
 }
 
 - (void)applicationWillTerminate:(UIApplication *)application {
-	ios_finish();
+	apple_embedded_finish();
 }
 
 // When application goes to background (e.g. user switches to another app or presses Home),
@@ -164,19 +163,19 @@ static ViewController *mainViewController = nil;
 // notification panel by swiping from the upper part of the screen.
 
 - (void)applicationWillResignActive:(UIApplication *)application {
-	OS_IOS::get_singleton()->on_focus_out();
+	OS_AppleEmbedded::get_singleton()->on_focus_out();
 }
 
 - (void)applicationDidBecomeActive:(UIApplication *)application {
-	OS_IOS::get_singleton()->on_focus_in();
+	OS_AppleEmbedded::get_singleton()->on_focus_in();
 }
 
 - (void)applicationDidEnterBackground:(UIApplication *)application {
-	OS_IOS::get_singleton()->on_enter_background();
+	OS_AppleEmbedded::get_singleton()->on_enter_background();
 }
 
 - (void)applicationWillEnterForeground:(UIApplication *)application {
-	OS_IOS::get_singleton()->on_exit_background();
+	OS_AppleEmbedded::get_singleton()->on_exit_background();
 }
 
 - (void)dealloc {

+ 59 - 0
drivers/apple_embedded/apple_embedded.h

@@ -0,0 +1,59 @@
+/**************************************************************************/
+/*  apple_embedded.h                                                      */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include "core/object/class_db.h"
+
+#import <CoreHaptics/CoreHaptics.h>
+
+class AppleEmbedded : public Object {
+	GDCLASS(AppleEmbedded, Object);
+
+	static void _bind_methods();
+
+private:
+	CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
+
+	CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
+	void start_haptic_engine();
+	void stop_haptic_engine();
+
+public:
+	static void alert(const char *p_alert, const char *p_title);
+
+	bool supports_haptic_engine();
+	void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude);
+
+	String get_model() const;
+	String get_rate_url(int p_app_id) const;
+
+	AppleEmbedded();
+};

+ 21 - 21
platform/ios/ios.mm → drivers/apple_embedded/apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  ios.mm                                                                */
+/*  apple_embedded.mm                                                     */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,23 +28,23 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "ios.h"
+#import "apple_embedded.h"
 
-#import "app_delegate.h"
+#import "app_delegate_service.h"
 #import "view_controller.h"
 
 #import <CoreHaptics/CoreHaptics.h>
 #import <UIKit/UIKit.h>
 #include <sys/sysctl.h>
 
-void iOS::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
-	ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine);
-	ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine);
-	ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine);
+void AppleEmbedded::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &AppleEmbedded::get_rate_url);
+	ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &AppleEmbedded::supports_haptic_engine);
+	ClassDB::bind_method(D_METHOD("start_haptic_engine"), &AppleEmbedded::start_haptic_engine);
+	ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &AppleEmbedded::stop_haptic_engine);
 }
 
-bool iOS::supports_haptic_engine() {
+bool AppleEmbedded::supports_haptic_engine() {
 	if (@available(iOS 13, *)) {
 		id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
 		return capabilities.supportsHaptics;
@@ -53,7 +53,7 @@ bool iOS::supports_haptic_engine() {
 	return false;
 }
 
-CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
+CHHapticEngine *AppleEmbedded::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
 	if (haptic_engine == nullptr) {
 		NSError *error = nullptr;
 		haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
@@ -69,7 +69,7 @@ CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
 	return haptic_engine;
 }
 
-void iOS::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) {
+void AppleEmbedded::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API_AVAILABLE(ios(13)) {
 	if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
 		if (supports_haptic_engine()) {
 			CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
@@ -117,10 +117,10 @@ void iOS::vibrate_haptic_engine(float p_duration_seconds, float p_amplitude) API
 		}
 	}
 
-	NSLog(@"Haptic engine is not supported in this version of iOS");
+	NSLog(@"Haptic engine is not supported");
 }
 
-void iOS::start_haptic_engine() {
+void AppleEmbedded::start_haptic_engine() {
 	if (@available(iOS 13, *)) {
 		if (supports_haptic_engine()) {
 			CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
@@ -136,10 +136,10 @@ void iOS::start_haptic_engine() {
 		}
 	}
 
-	NSLog(@"Haptic engine is not supported in this version of iOS");
+	NSLog(@"Haptic engine is not supported");
 }
 
-void iOS::stop_haptic_engine() {
+void AppleEmbedded::stop_haptic_engine() {
 	if (@available(iOS 13, *)) {
 		if (supports_haptic_engine()) {
 			CHHapticEngine *cur_haptic_engine = get_haptic_engine_instance();
@@ -155,10 +155,10 @@ void iOS::stop_haptic_engine() {
 		}
 	}
 
-	NSLog(@"Haptic engine is not supported in this version of iOS");
+	NSLog(@"Haptic engine is not supported");
 }
 
-void iOS::alert(const char *p_alert, const char *p_title) {
+void AppleEmbedded::alert(const char *p_alert, const char *p_title) {
 	NSString *title = [NSString stringWithUTF8String:p_title];
 	NSString *message = [NSString stringWithUTF8String:p_alert];
 
@@ -170,10 +170,10 @@ void iOS::alert(const char *p_alert, const char *p_title) {
 
 	[alert addAction:button];
 
-	[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
+	[GDTAppDelegateService.viewController presentViewController:alert animated:YES completion:nil];
 }
 
-String iOS::get_model() const {
+String AppleEmbedded::get_model() const {
 	// [[UIDevice currentDevice] model] only returns "iPad" or "iPhone".
 	size_t size;
 	sysctlbyname("hw.machine", nullptr, &size, nullptr, 0);
@@ -188,7 +188,7 @@ String iOS::get_model() const {
 	return String::utf8(str != nullptr ? str : "");
 }
 
-String iOS::get_rate_url(int p_app_id) const {
+String AppleEmbedded::get_rate_url(int p_app_id) const {
 	String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
 
 	String ret = app_url_path.replace("APP_ID", String::num_int64(p_app_id));
@@ -197,4 +197,4 @@ String iOS::get_rate_url(int p_app_id) const {
 	return ret;
 }
 
-iOS::iOS() {}
+AppleEmbedded::AppleEmbedded() {}

+ 9 - 20
platform/ios/main.m → drivers/apple_embedded/display_layer_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  main.m                                                                */
+/*  display_layer_apple_embedded.h                                        */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,26 +28,15 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "godot_app_delegate.h"
+#pragma once
 
-#import <UIKit/UIKit.h>
-#include <stdio.h>
+#import <QuartzCore/CAMetalLayer.h>
 
-int gargc;
-char **gargv;
+@protocol GDTDisplayLayer <NSObject>
 
-int main(int argc, char *argv[]) {
-#if defined(VULKAN_ENABLED)
-	//MoltenVK - enable full component swizzling support
-	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
-#endif
+- (void)startRenderDisplayLayer;
+- (void)stopRenderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
 
-	gargc = argc;
-	gargv = argv;
-
-	@autoreleasepool {
-		NSString *className = NSStringFromClass([GodotApplicationDelegate class]);
-		UIApplicationMain(argc, argv, nil, className);
-	}
-	return 0;
-}
+@end

+ 233 - 0
drivers/apple_embedded/display_server_apple_embedded.h

@@ -0,0 +1,233 @@
+/**************************************************************************/
+/*  display_server_apple_embedded.h                                       */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(RD_ENABLED)
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#include "servers/rendering/rendering_device.h"
+
+#if defined(VULKAN_ENABLED)
+#import "rendering_context_driver_vulkan_apple_embedded.h"
+
+#include "drivers/vulkan/godot_vulkan.h"
+#endif // VULKAN_ENABLED
+
+#if defined(METAL_ENABLED)
+#import "drivers/metal/rendering_context_driver_metal.h"
+#endif // METAL_ENABLED
+#endif // RD_ENABLED
+
+#if defined(GLES3_ENABLED)
+#include "drivers/gles3/rasterizer_gles3.h"
+#endif // GLES3_ENABLED
+
+#import <Foundation/Foundation.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+class DisplayServerAppleEmbedded : public DisplayServer {
+	GDSOFTCLASS(DisplayServerAppleEmbedded, DisplayServer);
+
+	_THREAD_SAFE_CLASS_
+
+#if defined(RD_ENABLED)
+	RenderingContextDriver *rendering_context = nullptr;
+	RenderingDevice *rendering_device = nullptr;
+#endif
+	NativeMenu *native_menu = nullptr;
+
+	id tts = nullptr;
+
+	DisplayServer::ScreenOrientation screen_orientation;
+
+	ObjectID window_attached_instance_id;
+
+	Callable window_event_callback;
+	Callable window_resize_callback;
+	Callable input_event_callback;
+	Callable input_text_callback;
+
+	Callable system_theme_changed;
+
+	int virtual_keyboard_height = 0;
+
+	void perform_event(const Ref<InputEvent> &p_event);
+
+	void initialize_tts() const;
+
+protected:
+	DisplayServerAppleEmbedded(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
+	~DisplayServerAppleEmbedded();
+
+public:
+	String rendering_driver;
+
+	static DisplayServerAppleEmbedded *get_singleton();
+
+	static Vector<String> get_rendering_drivers_func();
+
+	// MARK: - Events
+
+	virtual void process_events() override;
+
+	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+	void send_input_event(const Ref<InputEvent> &p_event) const;
+	void send_input_text(const String &p_text) const;
+	void send_window_event(DisplayServer::WindowEvent p_event) const;
+	void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+	void emit_system_theme_changed();
+
+	// MARK: - Input
+
+	// MARK: Touches and Apple Pencil
+
+	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
+	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt);
+	void touches_canceled(int p_idx);
+
+	// MARK: Keyboard
+
+	void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
+	bool is_keyboard_active() const;
+
+	// MARK: Motion
+
+	void update_gravity(const Vector3 &p_gravity);
+	void update_accelerometer(const Vector3 &p_accelerometer);
+	void update_magnetometer(const Vector3 &p_magnetometer);
+	void update_gyroscope(const Vector3 &p_gyroscope);
+
+	// MARK: -
+
+	virtual bool has_feature(Feature p_feature) const override;
+
+	virtual bool tts_is_speaking() const override;
+	virtual bool tts_is_paused() const override;
+	virtual TypedArray<Dictionary> tts_get_voices() const override;
+
+	virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+	virtual void tts_pause() override;
+	virtual void tts_resume() override;
+	virtual void tts_stop() override;
+
+	virtual bool is_dark_mode_supported() const override;
+	virtual bool is_dark_mode() const override;
+	virtual void set_system_theme_change_callback(const Callable &p_callable) override;
+
+	virtual Rect2i get_display_safe_area() const override;
+
+	virtual int get_screen_count() const override;
+	virtual int get_primary_screen() const override;
+	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+	virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+	virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+	virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
+	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+	virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+	virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual float screen_get_max_scale() const override;
+
+	virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
+	virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
+
+	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+	virtual bool can_any_window_draw() const override;
+
+	virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+	virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+
+	virtual bool is_touchscreen_available() const override;
+
+	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+	virtual void virtual_keyboard_hide() override;
+
+	void virtual_keyboard_set_height(int height);
+	virtual int virtual_keyboard_get_height() const override;
+	virtual bool has_hardware_keyboard() const override;
+
+	virtual void clipboard_set(const String &p_text) override;
+	virtual String clipboard_get() const override;
+
+	virtual void screen_set_keep_on(bool p_enable) override;
+	virtual bool screen_is_kept_on() const override;
+
+	void resize_window(CGSize size);
+	virtual void swap_buffers() override {}
+};

+ 791 - 0
drivers/apple_embedded/display_server_apple_embedded.mm

@@ -0,0 +1,791 @@
+/**************************************************************************/
+/*  display_server_apple_embedded.mm                                      */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#import "display_server_apple_embedded.h"
+
+#import "app_delegate_service.h"
+#import "apple_embedded.h"
+#import "godot_view_apple_embedded.h"
+#import "key_mapping_apple_embedded.h"
+#import "keyboard_input_view.h"
+#import "os_apple_embedded.h"
+#import "tts_apple_embedded.h"
+#import "view_controller.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
+
+#import <GameController/GameController.h>
+
+static const float kDisplayServerIOSAcceleration = 1.f;
+
+DisplayServerAppleEmbedded *DisplayServerAppleEmbedded::get_singleton() {
+	return (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
+}
+
+DisplayServerAppleEmbedded::DisplayServerAppleEmbedded(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
+	KeyMappingAppleEmbedded::initialize();
+
+	rendering_driver = p_rendering_driver;
+
+	// Init TTS
+	bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
+	if (tts_enabled) {
+		initialize_tts();
+	}
+	native_menu = memnew(NativeMenu);
+
+	bool has_made_render_compositor_current = false;
+
+#if defined(RD_ENABLED)
+	rendering_context = nullptr;
+	rendering_device = nullptr;
+
+	CALayer *layer = nullptr;
+
+	union {
+#ifdef VULKAN_ENABLED
+		RenderingContextDriverVulkanAppleEmbedded::WindowPlatformData vulkan;
+#endif
+#ifdef METAL_ENABLED
+		GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability")
+		// Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
+		RenderingContextDriverMetal::WindowPlatformData metal;
+		GODOT_CLANG_WARNING_POP
+#endif
+	} wpd;
+
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+		layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+		if (!layer) {
+			ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer.");
+		}
+		wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
+		rendering_context = memnew(RenderingContextDriverVulkanAppleEmbedded);
+	}
+#endif
+#ifdef METAL_ENABLED
+	if (rendering_driver == "metal") {
+		if (@available(iOS 14.0, *)) {
+			layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"metal"];
+			wpd.metal.layer = (CAMetalLayer *)layer;
+			rendering_context = memnew(RenderingContextDriverMetal);
+		} else {
+			OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later.");
+			r_error = ERR_UNAVAILABLE;
+			return;
+		}
+	}
+#endif
+	if (rendering_context) {
+		if (rendering_context->initialize() != OK) {
+			memdelete(rendering_context);
+			rendering_context = nullptr;
+#if defined(GLES3_ENABLED)
+			bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");
+			if (fallback_to_opengl3 && rendering_driver != "opengl3") {
+				WARN_PRINT("Your device seem not to support MoltenVK or Metal, switching to OpenGL 3.");
+				rendering_driver = "opengl3";
+				OS::get_singleton()->set_current_rendering_method("gl_compatibility");
+				OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
+			} else
+#endif
+			{
+				ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
+				r_error = ERR_UNAVAILABLE;
+				return;
+			}
+		}
+	}
+
+	if (rendering_context) {
+		if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) {
+			ERR_PRINT(vformat("Failed to create %s window.", rendering_driver));
+			memdelete(rendering_context);
+			rendering_context = nullptr;
+			r_error = ERR_UNAVAILABLE;
+			return;
+		}
+
+		Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+		rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height);
+		rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
+
+		rendering_device = memnew(RenderingDevice);
+		if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+			rendering_device = nullptr;
+			memdelete(rendering_context);
+			rendering_context = nullptr;
+			r_error = ERR_UNAVAILABLE;
+			return;
+		}
+		rendering_device->screen_create(MAIN_WINDOW_ID);
+
+		RendererCompositorRD::make_current();
+		has_made_render_compositor_current = true;
+	}
+#endif
+
+#if defined(GLES3_ENABLED)
+	if (rendering_driver == "opengl3") {
+		CALayer *layer = [GDTAppDelegateService.viewController.godotView initializeRenderingForDriver:@"opengl3"];
+
+		if (!layer) {
+			ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer.");
+		}
+
+		RasterizerGLES3::make_current(false);
+		has_made_render_compositor_current = true;
+	}
+#endif
+
+	ERR_FAIL_COND_MSG(!has_made_render_compositor_current, vformat("Failed to make RendererCompositor current for rendering driver %s", rendering_driver));
+
+	bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
+	screen_set_keep_on(keep_screen_on);
+
+	Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+	r_error = OK;
+}
+
+DisplayServerAppleEmbedded::~DisplayServerAppleEmbedded() {
+	if (native_menu) {
+		memdelete(native_menu);
+		native_menu = nullptr;
+	}
+
+#if defined(RD_ENABLED)
+	if (rendering_device) {
+		rendering_device->screen_free(MAIN_WINDOW_ID);
+		memdelete(rendering_device);
+		rendering_device = nullptr;
+	}
+
+	if (rendering_context) {
+		rendering_context->window_destroy(MAIN_WINDOW_ID);
+		memdelete(rendering_context);
+		rendering_context = nullptr;
+	}
+#endif
+}
+
+Vector<String> DisplayServerAppleEmbedded::get_rendering_drivers_func() {
+	Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+	drivers.push_back("vulkan");
+#endif
+#if defined(METAL_ENABLED)
+	if (@available(ios 14.0, *)) {
+		drivers.push_back("metal");
+	}
+#endif
+#if defined(GLES3_ENABLED)
+	drivers.push_back("opengl3");
+#endif
+
+	return drivers;
+}
+
+// MARK: Events
+
+void DisplayServerAppleEmbedded::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+	window_resize_callback = p_callable;
+}
+
+void DisplayServerAppleEmbedded::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+	window_event_callback = p_callable;
+}
+void DisplayServerAppleEmbedded::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+	input_event_callback = p_callable;
+}
+
+void DisplayServerAppleEmbedded::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+	input_text_callback = p_callable;
+}
+
+void DisplayServerAppleEmbedded::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerAppleEmbedded::process_events() {
+	Input::get_singleton()->flush_buffered_events();
+}
+
+void DisplayServerAppleEmbedded::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+	DisplayServerAppleEmbedded::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerAppleEmbedded::send_input_event(const Ref<InputEvent> &p_event) const {
+	_window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerAppleEmbedded::send_input_text(const String &p_text) const {
+	_window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerAppleEmbedded::send_window_event(DisplayServer::WindowEvent p_event) const {
+	_window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerAppleEmbedded::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+	if (p_callable.is_valid()) {
+		p_callable.call(p_arg);
+	}
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerAppleEmbedded::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
+	Ref<InputEventScreenTouch> ev;
+	ev.instantiate();
+
+	ev->set_index(p_idx);
+	ev->set_pressed(p_pressed);
+	ev->set_position(Vector2(p_x, p_y));
+	ev->set_double_tap(p_double_click);
+	perform_event(ev);
+}
+
+void DisplayServerAppleEmbedded::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) {
+	Ref<InputEventScreenDrag> ev;
+	ev.instantiate();
+	ev->set_index(p_idx);
+	ev->set_pressure(p_pressure);
+	ev->set_tilt(p_tilt);
+	ev->set_position(Vector2(p_x, p_y));
+	ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+	ev->set_relative_screen_position(ev->get_relative());
+	perform_event(ev);
+}
+
+void DisplayServerAppleEmbedded::perform_event(const Ref<InputEvent> &p_event) {
+	Input::get_singleton()->parse_input_event(p_event);
+}
+
+void DisplayServerAppleEmbedded::touches_canceled(int p_idx) {
+	touch_press(p_idx, -1, -1, false, false);
+}
+
+// MARK: Keyboard
+
+void DisplayServerAppleEmbedded::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
+	Ref<InputEventKey> ev;
+	ev.instantiate();
+	ev->set_echo(false);
+	ev->set_pressed(p_pressed);
+	ev->set_keycode(fix_keycode(p_char, p_key));
+	if (@available(iOS 13.4, *)) {
+		if (p_key != Key::SHIFT) {
+			ev->set_shift_pressed(p_modifier & UIKeyModifierShift);
+		}
+		if (p_key != Key::CTRL) {
+			ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl);
+		}
+		if (p_key != Key::ALT) {
+			ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate);
+		}
+		if (p_key != Key::META) {
+			ev->set_meta_pressed(p_modifier & UIKeyModifierCommand);
+		}
+	}
+	ev->set_key_label(p_unshifted);
+	ev->set_physical_keycode(p_physical);
+	ev->set_unicode(fix_unicode(p_char));
+	ev->set_location(p_location);
+	perform_event(ev);
+}
+
+// MARK: Motion
+
+void DisplayServerAppleEmbedded::update_gravity(const Vector3 &p_gravity) {
+	Input::get_singleton()->set_gravity(p_gravity);
+}
+
+void DisplayServerAppleEmbedded::update_accelerometer(const Vector3 &p_accelerometer) {
+	Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration);
+}
+
+void DisplayServerAppleEmbedded::update_magnetometer(const Vector3 &p_magnetometer) {
+	Input::get_singleton()->set_magnetometer(p_magnetometer);
+}
+
+void DisplayServerAppleEmbedded::update_gyroscope(const Vector3 &p_gyroscope) {
+	Input::get_singleton()->set_gyroscope(p_gyroscope);
+}
+
+// MARK: -
+
+bool DisplayServerAppleEmbedded::has_feature(Feature p_feature) const {
+	switch (p_feature) {
+#ifndef DISABLE_DEPRECATED
+		case FEATURE_GLOBAL_MENU: {
+			return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
+		} break;
+#endif
+		// case FEATURE_CURSOR_SHAPE:
+		// case FEATURE_CUSTOM_CURSOR_SHAPE:
+		// case FEATURE_HIDPI:
+		// case FEATURE_ICON:
+		// case FEATURE_IME:
+		// case FEATURE_MOUSE:
+		// case FEATURE_MOUSE_WARP:
+		// case FEATURE_NATIVE_DIALOG:
+		// case FEATURE_NATIVE_DIALOG_INPUT:
+		// case FEATURE_NATIVE_DIALOG_FILE:
+		// case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
+		// case FEATURE_NATIVE_DIALOG_FILE_MIME:
+		// case FEATURE_NATIVE_ICON:
+		// case FEATURE_WINDOW_TRANSPARENCY:
+		case FEATURE_CLIPBOARD:
+		case FEATURE_KEEP_SCREEN_ON:
+		case FEATURE_ORIENTATION:
+		case FEATURE_TOUCHSCREEN:
+		case FEATURE_VIRTUAL_KEYBOARD:
+		case FEATURE_TEXT_TO_SPEECH:
+			return true;
+		default:
+			return false;
+	}
+}
+
+void DisplayServerAppleEmbedded::initialize_tts() const {
+	const_cast<DisplayServerAppleEmbedded *>(this)->tts = [[GDTTTS alloc] init];
+}
+
+bool DisplayServerAppleEmbedded::tts_is_speaking() const {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL_V(tts, false);
+	return [tts isSpeaking];
+}
+
+bool DisplayServerAppleEmbedded::tts_is_paused() const {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL_V(tts, false);
+	return [tts isPaused];
+}
+
+TypedArray<Dictionary> DisplayServerAppleEmbedded::tts_get_voices() const {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
+	return [tts getVoices];
+}
+
+void DisplayServerAppleEmbedded::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL(tts);
+	[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
+}
+
+void DisplayServerAppleEmbedded::tts_pause() {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL(tts);
+	[tts pauseSpeaking];
+}
+
+void DisplayServerAppleEmbedded::tts_resume() {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL(tts);
+	[tts resumeSpeaking];
+}
+
+void DisplayServerAppleEmbedded::tts_stop() {
+	if (unlikely(!tts)) {
+		initialize_tts();
+	}
+	ERR_FAIL_NULL(tts);
+	[tts stopSpeaking];
+}
+
+bool DisplayServerAppleEmbedded::is_dark_mode_supported() const {
+	if (@available(iOS 13.0, *)) {
+		return true;
+	} else {
+		return false;
+	}
+}
+
+bool DisplayServerAppleEmbedded::is_dark_mode() const {
+	if (@available(iOS 13.0, *)) {
+		return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark;
+	} else {
+		return false;
+	}
+}
+
+void DisplayServerAppleEmbedded::set_system_theme_change_callback(const Callable &p_callable) {
+	system_theme_changed = p_callable;
+}
+
+void DisplayServerAppleEmbedded::emit_system_theme_changed() {
+	if (system_theme_changed.is_valid()) {
+		Variant ret;
+		Callable::CallError ce;
+		system_theme_changed.callp(nullptr, 0, ret, ce);
+		if (ce.error != Callable::CallError::CALL_OK) {
+			ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));
+		}
+	}
+}
+
+Rect2i DisplayServerAppleEmbedded::get_display_safe_area() const {
+	UIEdgeInsets insets = UIEdgeInsetsZero;
+	UIView *view = GDTAppDelegateService.viewController.godotView;
+	if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+		insets = [view safeAreaInsets];
+	}
+	float scale = screen_get_scale();
+	Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+	Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+	return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
+}
+
+int DisplayServerAppleEmbedded::get_screen_count() const {
+	return 1;
+}
+
+int DisplayServerAppleEmbedded::get_primary_screen() const {
+	return 0;
+}
+
+Point2i DisplayServerAppleEmbedded::screen_get_position(int p_screen) const {
+	return Size2i();
+}
+
+Size2i DisplayServerAppleEmbedded::screen_get_size(int p_screen) const {
+	CALayer *layer = GDTAppDelegateService.viewController.godotView.renderingLayer;
+
+	if (!layer) {
+		return Size2i();
+	}
+
+	return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+Rect2i DisplayServerAppleEmbedded::screen_get_usable_rect(int p_screen) const {
+	return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+}
+
+Vector<DisplayServer::WindowID> DisplayServerAppleEmbedded::get_window_list() const {
+	Vector<DisplayServer::WindowID> list;
+	list.push_back(MAIN_WINDOW_ID);
+	return list;
+}
+
+DisplayServer::WindowID DisplayServerAppleEmbedded::get_window_at_screen_position(const Point2i &p_position) const {
+	return MAIN_WINDOW_ID;
+}
+
+int64_t DisplayServerAppleEmbedded::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+	ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
+	switch (p_handle_type) {
+		case DISPLAY_HANDLE: {
+			return 0; // Not supported.
+		}
+		case WINDOW_HANDLE: {
+			return (int64_t)GDTAppDelegateService.viewController;
+		}
+		case WINDOW_VIEW: {
+			return (int64_t)GDTAppDelegateService.viewController.godotView;
+		}
+		default: {
+			return 0;
+		}
+	}
+}
+
+void DisplayServerAppleEmbedded::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+	window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerAppleEmbedded::window_get_attached_instance_id(WindowID p_window) const {
+	return window_attached_instance_id;
+}
+
+void DisplayServerAppleEmbedded::window_set_title(const String &p_title, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+int DisplayServerAppleEmbedded::window_get_current_screen(WindowID p_window) const {
+	return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerAppleEmbedded::window_set_current_screen(int p_screen, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Point2i DisplayServerAppleEmbedded::window_get_position(WindowID p_window) const {
+	return Point2i();
+}
+
+Point2i DisplayServerAppleEmbedded::window_get_position_with_decorations(WindowID p_window) const {
+	return Point2i();
+}
+
+void DisplayServerAppleEmbedded::window_set_position(const Point2i &p_position, WindowID p_window) {
+	// Probably not supported for single window iOS app
+}
+
+void DisplayServerAppleEmbedded::window_set_transient(WindowID p_window, WindowID p_parent) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerAppleEmbedded::window_set_max_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerAppleEmbedded::window_get_max_size(WindowID p_window) const {
+	return Size2i();
+}
+
+void DisplayServerAppleEmbedded::window_set_min_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerAppleEmbedded::window_get_min_size(WindowID p_window) const {
+	return Size2i();
+}
+
+void DisplayServerAppleEmbedded::window_set_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerAppleEmbedded::window_get_size(WindowID p_window) const {
+	id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
+	CGRect windowBounds = appDelegate.window.bounds;
+	return Size2i(windowBounds.size.width, windowBounds.size.height) * screen_get_max_scale();
+}
+
+Size2i DisplayServerAppleEmbedded::window_get_size_with_decorations(WindowID p_window) const {
+	return window_get_size(p_window);
+}
+
+void DisplayServerAppleEmbedded::window_set_mode(WindowMode p_mode, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerAppleEmbedded::window_get_mode(WindowID p_window) const {
+	return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerAppleEmbedded::window_is_maximize_allowed(WindowID p_window) const {
+	return false;
+}
+
+void DisplayServerAppleEmbedded::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+bool DisplayServerAppleEmbedded::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+	return false;
+}
+
+void DisplayServerAppleEmbedded::window_request_attention(WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerAppleEmbedded::window_move_to_foreground(WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+bool DisplayServerAppleEmbedded::window_is_focused(WindowID p_window) const {
+	return true;
+}
+
+float DisplayServerAppleEmbedded::screen_get_max_scale() const {
+	return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+}
+
+void DisplayServerAppleEmbedded::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+	screen_orientation = p_orientation;
+	if (@available(iOS 16.0, *)) {
+		[GDTAppDelegateService.viewController setNeedsUpdateOfSupportedInterfaceOrientations];
+	} else {
+		[UIViewController attemptRotationToDeviceOrientation];
+	}
+}
+
+DisplayServer::ScreenOrientation DisplayServerAppleEmbedded::screen_get_orientation(int p_screen) const {
+	return screen_orientation;
+}
+
+bool DisplayServerAppleEmbedded::window_can_draw(WindowID p_window) const {
+	return true;
+}
+
+bool DisplayServerAppleEmbedded::can_any_window_draw() const {
+	return true;
+}
+
+bool DisplayServerAppleEmbedded::is_touchscreen_available() const {
+	return true;
+}
+
+_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) {
+	int limit = p_pos;
+	for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) {
+		if (p_existing_text[i] > 0xffff) {
+			limit++;
+		}
+	}
+	return limit;
+}
+
+void DisplayServerAppleEmbedded::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) {
+	NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
+
+	GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
+	GDTAppDelegateService.viewController.keyboardView.textContentType = nil;
+	switch (p_type) {
+		case KEYBOARD_TYPE_DEFAULT: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
+		} break;
+		case KEYBOARD_TYPE_MULTILINE: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
+		} break;
+		case KEYBOARD_TYPE_NUMBER: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad;
+		} break;
+		case KEYBOARD_TYPE_NUMBER_DECIMAL: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad;
+		} break;
+		case KEYBOARD_TYPE_PHONE: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad;
+			GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber;
+		} break;
+		case KEYBOARD_TYPE_EMAIL_ADDRESS: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress;
+			GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress;
+		} break;
+		case KEYBOARD_TYPE_PASSWORD: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
+			GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypePassword;
+		} break;
+		case KEYBOARD_TYPE_URL: {
+			GDTAppDelegateService.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch;
+			GDTAppDelegateService.viewController.keyboardView.textContentType = UITextContentTypeURL;
+		} break;
+	}
+
+	[GDTAppDelegateService.viewController.keyboardView
+			becomeFirstResponderWithString:existingString
+							   cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start)
+								 cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)];
+}
+
+bool DisplayServerAppleEmbedded::is_keyboard_active() const {
+	return [GDTAppDelegateService.viewController.keyboardView isFirstResponder];
+}
+
+void DisplayServerAppleEmbedded::virtual_keyboard_hide() {
+	[GDTAppDelegateService.viewController.keyboardView resignFirstResponder];
+}
+
+void DisplayServerAppleEmbedded::virtual_keyboard_set_height(int height) {
+	virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerAppleEmbedded::virtual_keyboard_get_height() const {
+	return virtual_keyboard_height;
+}
+
+bool DisplayServerAppleEmbedded::has_hardware_keyboard() const {
+	if (@available(iOS 14.0, *)) {
+		return [GCKeyboard coalescedKeyboard];
+	} else {
+		return false;
+	}
+}
+
+void DisplayServerAppleEmbedded::clipboard_set(const String &p_text) {
+	[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()];
+}
+
+String DisplayServerAppleEmbedded::clipboard_get() const {
+	NSString *text = [UIPasteboard generalPasteboard].string;
+
+	return String::utf8([text UTF8String]);
+}
+
+void DisplayServerAppleEmbedded::screen_set_keep_on(bool p_enable) {
+	[UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerAppleEmbedded::screen_is_kept_on() const {
+	return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+void DisplayServerAppleEmbedded::resize_window(CGSize viewSize) {
+	Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
+
+#if defined(RD_ENABLED)
+	if (rendering_context) {
+		rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y);
+	}
+#endif
+
+	Variant resize_rect = Rect2i(Point2i(), size);
+	_window_callback(window_resize_callback, resize_rect);
+}
+
+void DisplayServerAppleEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
+	_THREAD_SAFE_METHOD_
+#if defined(RD_ENABLED)
+	if (rendering_context) {
+		rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
+	}
+#endif
+}
+
+DisplayServer::VSyncMode DisplayServerAppleEmbedded::window_get_vsync_mode(WindowID p_window) const {
+	_THREAD_SAFE_METHOD_
+#if defined(RD_ENABLED)
+	if (rendering_context) {
+		return rendering_context->window_get_vsync_mode(p_window);
+	}
+#endif
+	return DisplayServer::VSYNC_ENABLED;
+}

+ 4 - 4
platform/ios/godot_app_delegate.h → drivers/apple_embedded/godot_app_delegate.h

@@ -32,12 +32,12 @@
 
 #import <UIKit/UIKit.h>
 
-typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
+typedef NSObject<UIApplicationDelegate> GDTAppDelegateServiceProtocol;
 
-@interface GodotApplicationDelegate : NSObject <UIApplicationDelegate>
+@interface GDTApplicationDelegate : NSObject <UIApplicationDelegate>
 
-@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
+@property(class, readonly, strong) NSArray<GDTAppDelegateServiceProtocol *> *services;
 
-+ (void)addService:(ApplicationDelegateService *)service;
++ (void)addService:(GDTAppDelegateServiceProtocol *)service;
 
 @end

+ 36 - 40
platform/ios/godot_app_delegate.m → drivers/apple_embedded/godot_app_delegate.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_app_delegate.m                                                  */
+/*  godot_app_delegate.mm                                                 */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -30,26 +30,22 @@
 
 #import "godot_app_delegate.h"
 
-#import "app_delegate.h"
+#import "app_delegate_service.h"
 
-@interface GodotApplicationDelegate ()
+@implementation GDTApplicationDelegate
 
-@end
-
-@implementation GodotApplicationDelegate
-
-static NSMutableArray<ApplicationDelegateService *> *services = nil;
+static NSMutableArray<GDTAppDelegateServiceProtocol *> *services = nil;
 
-+ (NSArray<ApplicationDelegateService *> *)services {
++ (NSArray<GDTAppDelegateServiceProtocol *> *)services {
 	return services;
 }
 
 + (void)load {
 	services = [NSMutableArray new];
-	[services addObject:[AppDelegate new]];
+	[services addObject:[GDTAppDelegateService new]];
 }
 
-+ (void)addService:(ApplicationDelegateService *)service {
++ (void)addService:(GDTAppDelegateServiceProtocol *)service {
 	if (!services || !service) {
 		return;
 	}
@@ -63,7 +59,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (UIWindow *)window {
 	UIWindow *result = nil;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -83,7 +79,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -99,7 +95,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -125,7 +121,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: Life-Cycle
 
 - (void)applicationDidBecomeActive:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -135,7 +131,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationWillResignActive:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -145,7 +141,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationDidEnterBackground:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -155,7 +151,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationWillEnterForeground:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -165,7 +161,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationWillTerminate:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -177,7 +173,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: Environment Changes
 
 - (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -187,7 +183,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -197,7 +193,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -207,7 +203,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)applicationSignificantTimeChange:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -221,7 +217,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -237,7 +233,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -251,7 +247,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -267,7 +263,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -277,7 +273,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -289,7 +285,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: Download Data in Background
 
 - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -309,7 +305,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -325,7 +321,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -339,7 +335,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -349,7 +345,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -359,7 +355,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 }
 
 - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -371,7 +367,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: WatchKit
 
 - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -383,7 +379,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: HealthKit
 
 - (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -395,7 +391,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: Opening an URL
 
 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -413,7 +409,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 - (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
 	BOOL result = NO;
 
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -429,7 +425,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: SiriKit
 
 - (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}
@@ -447,7 +443,7 @@ static NSMutableArray<ApplicationDelegateService *> *services = nil;
 // MARK: CloudKit
 
 - (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
-	for (ApplicationDelegateService *service in services) {
+	for (GDTAppDelegateServiceProtocol *service in services) {
 		if (![service respondsToSelector:_cmd]) {
 			continue;
 		}

+ 19 - 11
platform/ios/godot_view.h → drivers/apple_embedded/godot_view_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_view.h                                                          */
+/*  godot_view_apple_embedded.h                                           */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -34,31 +34,39 @@
 
 class String;
 
-@class GodotView;
-@protocol DisplayLayer;
-@protocol GodotViewRendererProtocol;
+@class GDTView;
+@protocol GDTDisplayLayer;
+@protocol GDTViewRendererProtocol;
 
-@protocol GodotViewDelegate
+@protocol GDTViewDelegate
 
-- (BOOL)godotViewFinishedSetup:(GodotView *)view;
+- (BOOL)godotViewFinishedSetup:(GDTView *)view;
 
 @end
 
-@interface GodotView : UIView
+@interface GDTView : UIView
 
-@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
-@property(assign, nonatomic) id<GodotViewDelegate> delegate;
+@property(assign, nonatomic) id<GDTViewRendererProtocol> renderer;
+@property(assign, nonatomic) id<GDTViewDelegate> delegate;
 
 @property(assign, readonly, nonatomic) BOOL isActive;
 
 @property(assign, nonatomic) BOOL useCADisplayLink;
-@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(strong, readonly, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
 @property(assign, readonly, nonatomic) BOOL canRender;
 
 @property(assign, nonatomic) NSTimeInterval renderingInterval;
 
-- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+// Can be extended by subclasses
+- (void)godot_commonInit;
+
+// Implemented in subclasses
+- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+
 - (void)stopRendering;
 - (void)startRendering;
 
 @end
+
+// Implemented in subclasses
+extern GDTView *GDTViewCreate();

+ 43 - 64
platform/ios/godot_view.mm → drivers/apple_embedded/godot_view_apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_view.mm                                                         */
+/*  godot_view_apple_embedded.mm                                          */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,10 +28,10 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "godot_view.h"
+#import "godot_view_apple_embedded.h"
 
-#import "display_layer.h"
-#import "display_server_ios.h"
+#import "display_layer_apple_embedded.h"
+#import "display_server_apple_embedded.h"
 #import "godot_view_renderer.h"
 
 #include "core/config/project_settings.h"
@@ -43,7 +43,7 @@
 static const int max_touches = 32;
 static const float earth_gravity = 9.80665;
 
-@interface GodotView () {
+@interface GDTView () {
 	UITouch *godot_touches[max_touches];
 }
 
@@ -56,48 +56,17 @@ static const float earth_gravity = 9.80665;
 // Only used if CADisplayLink is not
 @property(strong, nonatomic) NSTimer *animationTimer;
 
-@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(strong, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
 
 @property(strong, nonatomic) CMMotionManager *motionManager;
 
 @end
 
-@implementation GodotView
+@implementation GDTView
 
-- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
-	if (self.renderingLayer) {
-		return self.renderingLayer;
-	}
-
-	CALayer<DisplayLayer> *layer;
-
-	if ([driverName isEqualToString:@"vulkan"] || [driverName isEqualToString:@"metal"]) {
-#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
-		if (@available(iOS 13, *)) {
-			layer = [GodotMetalLayer layer];
-		} else {
-			return nil;
-		}
-#else
-		layer = [GodotMetalLayer layer];
-#endif
-	} else if ([driverName isEqualToString:@"opengl3"]) {
-		GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in iOS 12.0.
-		layer = [GodotOpenGLLayer layer];
-		GODOT_CLANG_WARNING_POP
-	} else {
-		return nil;
-	}
-
-	layer.frame = self.bounds;
-	layer.contentsScale = self.contentScaleFactor;
-
-	[self.layer addSublayer:layer];
-	self.renderingLayer = layer;
-
-	[layer initializeDisplayLayer];
-
-	return self.renderingLayer;
+// Implemented in subclasses
+- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+	return nil;
 }
 
 - (instancetype)initWithCoder:(NSCoder *)coder {
@@ -150,6 +119,10 @@ static const float earth_gravity = 9.80665;
 - (void)godot_commonInit {
 	self.contentScaleFactor = [UIScreen mainScreen].scale;
 
+	if (@available(iOS 17.0, *)) {
+		[self registerForTraitChanges:@[ [UITraitUserInterfaceStyle class] ] withTarget:self action:@selector(traitCollectionDidChangeWithView:previousTraitCollection:)];
+	}
+
 	[self initTouches];
 
 	self.multipleTouchEnabled = YES;
@@ -167,7 +140,7 @@ static const float earth_gravity = 9.80665;
 }
 
 - (void)system_theme_changed {
-	DisplayServerIOS *ds = (DisplayServerIOS *)DisplayServer::get_singleton();
+	DisplayServerAppleEmbedded *ds = (DisplayServerAppleEmbedded *)DisplayServer::get_singleton();
 	if (ds) {
 		ds->emit_system_theme_changed();
 	}
@@ -176,7 +149,13 @@ static const float earth_gravity = 9.80665;
 - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
 	if (@available(iOS 13.0, *)) {
 		[super traitCollectionDidChange:previousTraitCollection];
+		[self traitCollectionDidChangeWithView:self
+					   previousTraitCollection:previousTraitCollection];
+	}
+}
 
+- (void)traitCollectionDidChangeWithView:(UIView *)view previousTraitCollection:(UITraitCollection *)previousTraitCollection {
+	if (@available(iOS 13.0, *)) {
 		if ([UITraitCollection currentTraitCollection].userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
 			[self system_theme_changed];
 		}
@@ -293,8 +272,8 @@ static const float earth_gravity = 9.80665;
 		self.renderingLayer.frame = self.bounds;
 		[self.renderingLayer layoutDisplayLayer];
 
-		if (DisplayServerIOS::get_singleton()) {
-			DisplayServerIOS::get_singleton()->resize_window(self.bounds.size);
+		if (DisplayServerAppleEmbedded::get_singleton()) {
+			DisplayServerAppleEmbedded::get_singleton()->resize_window(self.bounds.size);
 		}
 	}
 
@@ -357,7 +336,7 @@ static const float earth_gravity = 9.80665;
 		int tid = [self getTouchIDForTouch:touch];
 		ERR_FAIL_COND(tid == -1);
 		CGPoint touchPoint = [touch locationInView:self];
-		DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+		DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
 	}
 }
 
@@ -369,7 +348,7 @@ static const float earth_gravity = 9.80665;
 		CGPoint prev_point = [touch previousLocationInView:self];
 		CGFloat alt = [touch altitudeAngle];
 		CGVector azim = [touch azimuthUnitVectorInView:self];
-		DisplayServerIOS::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt));
+		DisplayServerAppleEmbedded::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, [touch force] / [touch maximumPossibleForce], Vector2(azim.dx, azim.dy) * Math::cos(alt));
 	}
 }
 
@@ -379,7 +358,7 @@ static const float earth_gravity = 9.80665;
 		ERR_FAIL_COND(tid == -1);
 		[self removeTouch:touch];
 		CGPoint touchPoint = [touch locationInView:self];
-		DisplayServerIOS::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+		DisplayServerAppleEmbedded::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
 	}
 }
 
@@ -387,7 +366,7 @@ static const float earth_gravity = 9.80665;
 	for (UITouch *touch in touches) {
 		int tid = [self getTouchIDForTouch:touch];
 		ERR_FAIL_COND(tid == -1);
-		DisplayServerIOS::get_singleton()->touches_canceled(tid);
+		DisplayServerAppleEmbedded::get_singleton()->touches_canceled(tid);
 	}
 	[self clearTouches];
 }
@@ -454,28 +433,28 @@ static const float earth_gravity = 9.80665;
 
 	switch (interfaceOrientation) {
 		case UIInterfaceOrientationLandscapeLeft: {
-			DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), -Math::PI * 0.5));
 		} break;
 		case UIInterfaceOrientationLandscapeRight: {
-			DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
-			DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
+			DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI * 0.5));
 		} break;
 		case UIInterfaceOrientationPortraitUpsideDown: {
-			DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
-			DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
-			DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI));
-			DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI));
+			DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
+			DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z).rotated(Vector3(0, 0, 1), Math::PI));
+			DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z).rotated(Vector3(0, 0, 1), Math::PI));
+			DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z).rotated(Vector3(0, 0, 1), Math::PI));
 		} break;
 		default: { // assume portrait
-			DisplayServerIOS::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z));
-			DisplayServerIOS::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z));
-			DisplayServerIOS::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z));
-			DisplayServerIOS::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z));
+			DisplayServerAppleEmbedded::get_singleton()->update_gravity(Vector3(gravity.x, gravity.y, gravity.z));
+			DisplayServerAppleEmbedded::get_singleton()->update_accelerometer(Vector3(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z));
+			DisplayServerAppleEmbedded::get_singleton()->update_magnetometer(Vector3(magnetic.x, magnetic.y, magnetic.z));
+			DisplayServerAppleEmbedded::get_singleton()->update_gyroscope(Vector3(rotation.x, rotation.y, rotation.z));
 		} break;
 	}
 }

+ 2 - 2
platform/ios/godot_view_renderer.h → drivers/apple_embedded/godot_view_renderer.h

@@ -32,7 +32,7 @@
 
 #import <UIKit/UIKit.h>
 
-@protocol GodotViewRendererProtocol <NSObject>
+@protocol GDTViewRendererProtocol <NSObject>
 
 @property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
 
@@ -41,6 +41,6 @@
 
 @end
 
-@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+@interface GDTViewRenderer : NSObject <GDTViewRendererProtocol>
 
 @end

+ 7 - 7
platform/ios/godot_view_renderer.mm → drivers/apple_embedded/godot_view_renderer.mm

@@ -30,8 +30,8 @@
 
 #import "godot_view_renderer.h"
 
-#import "display_server_ios.h"
-#import "os_ios.h"
+#import "display_server_apple_embedded.h"
+#import "os_apple_embedded.h"
 
 #include "core/config/project_settings.h"
 #include "core/os/keyboard.h"
@@ -44,7 +44,7 @@
 #import <QuartzCore/QuartzCore.h>
 #import <UIKit/UIKit.h>
 
-@interface GodotViewRenderer ()
+@interface GDTViewRenderer ()
 
 @property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
 @property(assign, nonatomic) BOOL hasStartedMain;
@@ -52,7 +52,7 @@
 
 @end
 
-@implementation GodotViewRenderer
+@implementation GDTViewRenderer
 
 - (BOOL)setupView:(UIView *)view {
 	if (self.hasFinishedSetup) {
@@ -70,7 +70,7 @@
 
 	if (!self.hasStartedMain) {
 		self.hasStartedMain = YES;
-		OS_IOS::get_singleton()->start();
+		OS_AppleEmbedded::get_singleton()->start();
 		return YES;
 	}
 
@@ -109,11 +109,11 @@
 }
 
 - (void)renderOnView:(UIView *)view {
-	if (!OS_IOS::get_singleton()) {
+	if (!OS_AppleEmbedded::get_singleton()) {
 		return;
 	}
 
-	OS_IOS::get_singleton()->iterate();
+	OS_AppleEmbedded::get_singleton()->iterate();
 }
 
 @end

+ 3 - 3
platform/ios/key_mapping_ios.h → drivers/apple_embedded/key_mapping_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  key_mapping_ios.h                                                     */
+/*  key_mapping_apple_embedded.h                                          */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -34,8 +34,8 @@
 
 #import <UIKit/UIKit.h>
 
-class KeyMappingIOS {
-	KeyMappingIOS() {}
+class KeyMappingAppleEmbedded {
+	KeyMappingAppleEmbedded() {}
 
 public:
 	static void initialize();

+ 5 - 5
platform/ios/key_mapping_ios.mm → drivers/apple_embedded/key_mapping_apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  key_mapping_ios.mm                                                    */
+/*  key_mapping_apple_embedded.mm                                         */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,7 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "key_mapping_ios.h"
+#import "key_mapping_apple_embedded.h"
 
 #include "core/templates/hash_map.h"
 
@@ -40,7 +40,7 @@ struct HashMapHasherKeys {
 HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
 HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
 
-void KeyMappingIOS::initialize() {
+void KeyMappingAppleEmbedded::initialize() {
 	if (@available(iOS 13.4, *)) {
 		keyusage_map[UIKeyboardHIDUsageKeyboardA] = Key::A;
 		keyusage_map[UIKeyboardHIDUsageKeyboardB] = Key::B;
@@ -185,7 +185,7 @@ void KeyMappingIOS::initialize() {
 	}
 }
 
-Key KeyMappingIOS::remap_key(CFIndex p_keycode) {
+Key KeyMappingAppleEmbedded::remap_key(CFIndex p_keycode) {
 	if (@available(iOS 13.4, *)) {
 		const Key *key = keyusage_map.getptr(p_keycode);
 		if (key) {
@@ -195,7 +195,7 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) {
 	return Key::NONE;
 }
 
-KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) {
+KeyLocation KeyMappingAppleEmbedded::key_location(CFIndex p_keycode) {
 	if (@available(iOS 13.4, *)) {
 		const KeyLocation *location = location_map.getptr(p_keycode);
 		if (location) {

+ 1 - 1
platform/ios/keyboard_input_view.h → drivers/apple_embedded/keyboard_input_view.h

@@ -32,7 +32,7 @@
 
 #import <UIKit/UIKit.h>
 
-@interface GodotKeyboardInputView : UITextView
+@interface GDTKeyboardInputView : UITextView
 
 - (BOOL)becomeFirstResponderWithString:(NSString *)existingString cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
 

+ 8 - 8
platform/ios/keyboard_input_view.mm → drivers/apple_embedded/keyboard_input_view.mm

@@ -30,19 +30,19 @@
 
 #import "keyboard_input_view.h"
 
-#import "display_server_ios.h"
-#import "os_ios.h"
+#import "display_server_apple_embedded.h"
+#import "os_apple_embedded.h"
 
 #include "core/os/keyboard.h"
 
-@interface GodotKeyboardInputView () <UITextViewDelegate>
+@interface GDTKeyboardInputView () <UITextViewDelegate>
 
 @property(nonatomic, copy) NSString *previousText;
 @property(nonatomic, assign) NSRange previousSelectedRange;
 
 @end
 
-@implementation GodotKeyboardInputView
+@implementation GDTKeyboardInputView
 
 - (instancetype)initWithCoder:(NSCoder *)coder {
 	self = [super initWithCoder:coder];
@@ -116,8 +116,8 @@
 
 - (void)deleteText:(NSInteger)charactersToDelete {
 	for (int i = 0; i < charactersToDelete; i++) {
-		DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
-		DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
+		DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+		DisplayServerAppleEmbedded::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
 	}
 }
 
@@ -136,8 +136,8 @@
 			key = Key::SPACE;
 		}
 
-		DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
-		DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
+		DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+		DisplayServerAppleEmbedded::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
 	}
 }
 

+ 35 - 0
drivers/apple_embedded/main_utilities.h

@@ -0,0 +1,35 @@
+/**************************************************************************/
+/*  main_utilities.h                                                      */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+void change_to_launch_dir(char **p_args);
+
+int process_args(int p_argc, char **p_args, char **r_args);

+ 26 - 50
platform/ios/godot_ios.mm → drivers/apple_embedded/main_utilities.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_ios.mm                                                          */
+/*  main_utilities.mm                                                     */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,15 +28,29 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "os_ios.h"
-
 #include "core/string/ustring.h"
-#include "main/main.h"
+
+#import <UIKit/UIKit.h>
 
 #include <unistd.h>
 #include <cstdio>
 
-static OS_IOS *os = nullptr;
+void change_to_launch_dir(char **p_args) {
+	size_t len = strlen(p_args[0]);
+
+	while (len--) {
+		if (p_args[0][len] == '/') {
+			break;
+		}
+	}
+
+	if (len >= 0) {
+		char path[512];
+		memcpy(path, p_args[0], len > sizeof(path) ? sizeof(path) : len);
+		path[len] = 0;
+		chdir(path);
+	}
+}
 
 int add_path(int p_argc, char **p_args) {
 	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
@@ -70,50 +84,12 @@ int add_cmdline(int p_argc, char **p_args) {
 	return p_argc;
 }
 
-int ios_main(int argc, char **argv) {
-	size_t len = strlen(argv[0]);
-
-	while (len--) {
-		if (argv[0][len] == '/') {
-			break;
-		}
+int process_args(int p_argc, char **p_args, char **r_args) {
+	for (int i = 0; i < p_argc; i++) {
+		r_args[i] = p_args[i];
 	}
-
-	if (len >= 0) {
-		char path[512];
-		memcpy(path, argv[0], len > sizeof(path) ? sizeof(path) : len);
-		path[len] = 0;
-		chdir(path);
-	}
-
-	os = new OS_IOS();
-
-	// We must override main when testing is enabled
-	TEST_MAIN_OVERRIDE
-
-	char *fargv[64];
-	for (int i = 0; i < argc; i++) {
-		fargv[i] = argv[i];
-	}
-	fargv[argc] = nullptr;
-	argc = add_path(argc, fargv);
-	argc = add_cmdline(argc, fargv);
-
-	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
-
-	if (err != OK) {
-		if (err == ERR_HELP) { // Returned by --help and --version, so success.
-			return EXIT_SUCCESS;
-		}
-		return EXIT_FAILURE;
-	}
-
-	os->initialize_modules();
-
-	return os->get_exit_code();
-}
-
-void ios_finish() {
-	Main::cleanup();
-	delete os;
+	r_args[p_argc] = nullptr;
+	p_argc = add_path(p_argc, r_args);
+	p_argc = add_cmdline(p_argc, r_args);
+	return p_argc;
 }

+ 137 - 0
drivers/apple_embedded/os_apple_embedded.h

@@ -0,0 +1,137 @@
+/**************************************************************************/
+/*  os_apple_embedded.h                                                   */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef APPLE_EMBEDDED_ENABLED
+
+#import "apple_embedded.h"
+
+#import "drivers/apple/joypad_apple.h"
+#import "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "drivers/unix/os_unix.h"
+#include "servers/audio_server.h"
+#include "servers/rendering/renderer_compositor.h"
+
+#if defined(RD_ENABLED)
+#include "servers/rendering/rendering_device.h"
+
+#if defined(VULKAN_ENABLED)
+#import "rendering_context_driver_vulkan_apple_embedded.h"
+#endif
+#endif
+
+class OS_AppleEmbedded : public OS_Unix {
+private:
+	static HashMap<String, void *> dynamic_symbol_lookup_table;
+	friend void register_dynamic_symbol(char *name, void *address);
+
+	AudioDriverCoreAudio audio_driver;
+
+	AppleEmbedded *apple_embedded = nullptr;
+
+	JoypadApple *joypad_apple = nullptr;
+
+	MainLoop *main_loop = nullptr;
+
+	virtual void initialize_core() override;
+	virtual void initialize() override;
+
+	virtual void initialize_joypads() override;
+
+	virtual void set_main_loop(MainLoop *p_main_loop) override;
+	virtual MainLoop *get_main_loop() const override;
+
+	virtual void delete_main_loop() override;
+
+	virtual void finalize() override;
+
+	bool is_focused = false;
+
+	CGFloat _weight_to_ct(int p_weight) const;
+	CGFloat _stretch_to_ct(int p_stretch) const;
+	String _get_default_fontname(const String &p_font_name) const;
+
+	static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
+
+	void deinitialize_modules();
+
+public:
+	static OS_AppleEmbedded *get_singleton();
+
+	OS_AppleEmbedded();
+	~OS_AppleEmbedded();
+
+	void initialize_modules();
+
+	bool iterate();
+
+	void start();
+
+	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+	virtual Vector<String> get_system_fonts() const override;
+	virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
+	virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
+
+	virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
+	virtual Error close_dynamic_library(void *p_library_handle) override;
+	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
+
+	virtual String get_distribution_name() const override;
+	virtual String get_version() const override;
+	virtual String get_model_name() const override;
+
+	virtual Error shell_open(const String &p_uri) override;
+
+	virtual String get_user_data_dir(const String &p_user_dir) const override;
+
+	virtual String get_cache_path() const override;
+	virtual String get_temp_path() const override;
+
+	virtual String get_locale() const override;
+
+	virtual String get_unique_id() const override;
+	virtual String get_processor_name() const override;
+
+	virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override;
+
+	virtual bool _check_internal_feature_support(const String &p_feature) override;
+
+	void on_focus_out();
+	void on_focus_in();
+
+	void on_enter_background();
+	void on_exit_background();
+
+	virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override;
+};
+
+#endif // APPLE_EMBEDDED_ENABLED

+ 717 - 0
drivers/apple_embedded/os_apple_embedded.mm

@@ -0,0 +1,717 @@
+/**************************************************************************/
+/*  os_apple_embedded.mm                                                  */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#import "os_apple_embedded.h"
+
+#ifdef APPLE_EMBEDDED_ENABLED
+
+#import "app_delegate_service.h"
+#import "display_server_apple_embedded.h"
+#import "godot_view_apple_embedded.h"
+#import "terminal_logger_apple_embedded.h"
+#import "view_controller.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/file_access_pack.h"
+#include "drivers/unix/syslog_logger.h"
+#include "main/main.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreText/CoreText.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+#include <sys/sysctl.h>
+
+#if defined(RD_ENABLED)
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/godot_vulkan.h"
+#endif // VULKAN_ENABLED
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *apple_init_callbacks = nullptr;
+static int apple_embedded_platform_init_callbacks_count = 0;
+static int apple_embedded_platform_init_callbacks_capacity = 0;
+HashMap<String, void *> OS_AppleEmbedded::dynamic_symbol_lookup_table;
+
+void add_apple_embedded_platform_init_callback(init_callback cb) {
+	if (apple_embedded_platform_init_callbacks_count == apple_embedded_platform_init_callbacks_capacity) {
+		void *new_ptr = realloc(apple_init_callbacks, sizeof(cb) * (apple_embedded_platform_init_callbacks_capacity + 32));
+		if (new_ptr) {
+			apple_init_callbacks = (init_callback *)(new_ptr);
+			apple_embedded_platform_init_callbacks_capacity += 32;
+		} else {
+			ERR_FAIL_MSG("Unable to allocate memory for extension callbacks.");
+		}
+	}
+	apple_init_callbacks[apple_embedded_platform_init_callbacks_count++] = cb;
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+	OS_AppleEmbedded::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) {
+	real_t available_ratio = p_container.width / p_container.height;
+	real_t fit_ratio = p_rect.width / p_rect.height;
+	Rect2 result;
+	if (fit_ratio < available_ratio) {
+		// Fit height - we'll have horizontal gaps
+		result.size.height = p_container.height;
+		result.size.width = p_container.height * fit_ratio;
+		result.position.y = 0;
+		result.position.x = (p_container.width - result.size.width) * 0.5f;
+	} else {
+		// Fit width - we'll have vertical gaps
+		result.size.width = p_container.width;
+		result.size.height = p_container.width / fit_ratio;
+		result.position.x = 0;
+		result.position.y = (p_container.height - result.size.height) * 0.5f;
+	}
+	return result;
+}
+
+Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) {
+	real_t available_ratio = p_container.width / p_container.height;
+	real_t fit_ratio = p_rect.width / p_rect.height;
+	Rect2 result;
+	if (fit_ratio < available_ratio) {
+		// Need to scale up to fit width, and crop height
+		result.size.width = p_container.width;
+		result.size.height = p_container.width / fit_ratio;
+		result.position.x = 0;
+		result.position.y = (p_container.height - result.size.height) * 0.5f;
+	} else {
+		// Need to scale up to fit height, and crop width
+		result.size.width = p_container.height * fit_ratio;
+		result.size.height = p_container.height;
+		result.position.x = (p_container.width - result.size.width) * 0.5f;
+		result.position.y = 0;
+	}
+	return result;
+}
+
+OS_AppleEmbedded *OS_AppleEmbedded::get_singleton() {
+	return (OS_AppleEmbedded *)OS::get_singleton();
+}
+
+OS_AppleEmbedded::OS_AppleEmbedded() {
+	for (int i = 0; i < apple_embedded_platform_init_callbacks_count; ++i) {
+		apple_init_callbacks[i]();
+	}
+	free(apple_init_callbacks);
+	apple_init_callbacks = nullptr;
+	apple_embedded_platform_init_callbacks_count = 0;
+	apple_embedded_platform_init_callbacks_capacity = 0;
+
+	main_loop = nullptr;
+
+	Vector<Logger *> loggers;
+	loggers.push_back(memnew(TerminalLoggerAppleEmbedded));
+	_set_logger(memnew(CompositeLogger(loggers)));
+
+	AudioDriverManager::add_driver(&audio_driver);
+}
+
+OS_AppleEmbedded::~OS_AppleEmbedded() {}
+
+void OS_AppleEmbedded::alert(const String &p_alert, const String &p_title) {
+	const CharString utf8_alert = p_alert.utf8();
+	const CharString utf8_title = p_title.utf8();
+	AppleEmbedded::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+void OS_AppleEmbedded::initialize_core() {
+	OS_Unix::initialize_core();
+}
+
+void OS_AppleEmbedded::initialize() {
+	initialize_core();
+}
+
+void OS_AppleEmbedded::initialize_joypads() {
+	joypad_apple = memnew(JoypadApple);
+}
+
+void OS_AppleEmbedded::initialize_modules() {
+	apple_embedded = memnew(AppleEmbedded);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("AppleEmbedded", apple_embedded));
+}
+
+void OS_AppleEmbedded::deinitialize_modules() {
+	if (joypad_apple) {
+		memdelete(joypad_apple);
+	}
+
+	if (apple_embedded) {
+		memdelete(apple_embedded);
+	}
+}
+
+void OS_AppleEmbedded::set_main_loop(MainLoop *p_main_loop) {
+	main_loop = p_main_loop;
+}
+
+MainLoop *OS_AppleEmbedded::get_main_loop() const {
+	return main_loop;
+}
+
+void OS_AppleEmbedded::delete_main_loop() {
+	if (main_loop) {
+		main_loop->finalize();
+		memdelete(main_loop);
+	}
+
+	main_loop = nullptr;
+}
+
+bool OS_AppleEmbedded::iterate() {
+	if (!main_loop) {
+		return true;
+	}
+
+	if (DisplayServer::get_singleton()) {
+		DisplayServer::get_singleton()->process_events();
+	}
+
+	joypad_apple->process_joypads();
+
+	return Main::iteration();
+}
+
+void OS_AppleEmbedded::start() {
+	if (Main::start() == EXIT_SUCCESS) {
+		main_loop->initialize();
+	}
+}
+
+void OS_AppleEmbedded::finalize() {
+	deinitialize_modules();
+
+	// Already gets called
+	//delete_main_loop();
+}
+
+// MARK: Dynamic Libraries
+
+_FORCE_INLINE_ String OS_AppleEmbedded::get_framework_executable(const String &p_path) {
+	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+	// Read framework bundle to get executable name.
+	NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
+	NSBundle *bundle = [NSBundle bundleWithURL:url];
+	if (bundle) {
+		String exe_path = String::utf8([[bundle executablePath] UTF8String]);
+		if (da->file_exists(exe_path)) {
+			return exe_path;
+		}
+	}
+
+	// Try default executable name (invalid framework).
+	if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
+		return p_path.path_join(p_path.get_file().get_basename());
+	}
+
+	// Not a framework, try loading as .dylib.
+	return p_path;
+}
+
+Error OS_AppleEmbedded::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
+	if (p_path.length() == 0) {
+		// Static xcframework.
+		p_library_handle = RTLD_SELF;
+
+		if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+			*p_data->r_resolved_path = p_path;
+		}
+
+		return OK;
+	}
+
+	String path = get_framework_executable(p_path);
+
+	if (!FileAccess::exists(path)) {
+		// Load .dylib or framework from within the executable path.
+		path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
+	}
+
+	if (!FileAccess::exists(path)) {
+		// Load .dylib converted to framework from within the executable path.
+		path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework"));
+	}
+
+	if (!FileAccess::exists(path)) {
+		// Load .dylib or framework from a standard iOS location.
+		path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file()));
+	}
+
+	if (!FileAccess::exists(path)) {
+		// Load .dylib converted to framework from a standard iOS location.
+		path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
+	}
+
+	ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
+
+	p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
+	ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
+
+	if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
+		*p_data->r_resolved_path = path;
+	}
+
+	return OK;
+}
+
+Error OS_AppleEmbedded::close_dynamic_library(void *p_library_handle) {
+	if (p_library_handle == RTLD_SELF) {
+		return OK;
+	}
+	return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+Error OS_AppleEmbedded::get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional) {
+	if (p_library_handle == RTLD_SELF) {
+		void **ptr = OS_AppleEmbedded::dynamic_symbol_lookup_table.getptr(p_name);
+		if (ptr) {
+			p_symbol_handle = *ptr;
+			return OK;
+		}
+	}
+	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+String OS_AppleEmbedded::get_distribution_name() const {
+	return get_name();
+}
+
+String OS_AppleEmbedded::get_version() const {
+	NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
+	return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
+}
+
+String OS_AppleEmbedded::get_model_name() const {
+	String model = apple_embedded->get_model();
+	if (model != "") {
+		return model;
+	}
+
+	return OS_Unix::get_model_name();
+}
+
+Error OS_AppleEmbedded::shell_open(const String &p_uri) {
+	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+	NSURL *url = [NSURL URLWithString:urlPath];
+
+	if (![[UIApplication sharedApplication] canOpenURL:url]) {
+		return ERR_CANT_OPEN;
+	}
+
+	print_verbose(vformat("Opening URL %s", p_uri));
+
+	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+
+	return OK;
+}
+
+String OS_AppleEmbedded::get_user_data_dir(const String &p_user_dir) const {
+	static String ret;
+	if (ret.is_empty()) {
+		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+		if (paths && [paths count] >= 1) {
+			ret.append_utf8([[paths firstObject] UTF8String]);
+		}
+	}
+	return ret;
+}
+
+String OS_AppleEmbedded::get_cache_path() const {
+	static String ret;
+	if (ret.is_empty()) {
+		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+		if (paths && [paths count] >= 1) {
+			ret.append_utf8([[paths firstObject] UTF8String]);
+		}
+	}
+	return ret;
+}
+
+String OS_AppleEmbedded::get_temp_path() const {
+	static String ret;
+	if (ret.is_empty()) {
+		NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
+								isDirectory:YES];
+		if (url) {
+			ret = String::utf8([url.path UTF8String]);
+			ret = ret.trim_prefix("file://");
+		}
+	}
+	return ret;
+}
+
+String OS_AppleEmbedded::get_locale() const {
+	NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject;
+
+	if (preferredLanguage) {
+		return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_');
+	}
+
+	NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
+	return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_');
+}
+
+String OS_AppleEmbedded::get_unique_id() const {
+	NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+	return String::utf8([uuid UTF8String]);
+}
+
+String OS_AppleEmbedded::get_processor_name() const {
+	char buffer[256];
+	size_t buffer_len = 256;
+	if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
+		return String::utf8(buffer, buffer_len);
+	}
+	ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
+}
+
+Vector<String> OS_AppleEmbedded::get_system_fonts() const {
+	HashSet<String> font_names;
+	CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
+	if (fonts) {
+		for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
+			CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
+			if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
+				NSString *ns_name = (__bridge NSString *)cf_name;
+				font_names.insert(String::utf8([ns_name UTF8String]));
+			}
+		}
+		CFRelease(fonts);
+	}
+
+	Vector<String> ret;
+	for (const String &E : font_names) {
+		ret.push_back(E);
+	}
+	return ret;
+}
+
+String OS_AppleEmbedded::_get_default_fontname(const String &p_font_name) const {
+	String font_name = p_font_name;
+	if (font_name.to_lower() == "sans-serif") {
+		font_name = "Helvetica";
+	} else if (font_name.to_lower() == "serif") {
+		font_name = "Times";
+	} else if (font_name.to_lower() == "monospace") {
+		font_name = "Courier";
+	} else if (font_name.to_lower() == "fantasy") {
+		font_name = "Papyrus";
+	} else if (font_name.to_lower() == "cursive") {
+		font_name = "Apple Chancery";
+	};
+	return font_name;
+}
+
+CGFloat OS_AppleEmbedded::_weight_to_ct(int p_weight) const {
+	if (p_weight < 150) {
+		return -0.80;
+	} else if (p_weight < 250) {
+		return -0.60;
+	} else if (p_weight < 350) {
+		return -0.40;
+	} else if (p_weight < 450) {
+		return 0.0;
+	} else if (p_weight < 550) {
+		return 0.23;
+	} else if (p_weight < 650) {
+		return 0.30;
+	} else if (p_weight < 750) {
+		return 0.40;
+	} else if (p_weight < 850) {
+		return 0.56;
+	} else if (p_weight < 925) {
+		return 0.62;
+	} else {
+		return 1.00;
+	}
+}
+
+CGFloat OS_AppleEmbedded::_stretch_to_ct(int p_stretch) const {
+	if (p_stretch < 56) {
+		return -0.5;
+	} else if (p_stretch < 69) {
+		return -0.37;
+	} else if (p_stretch < 81) {
+		return -0.25;
+	} else if (p_stretch < 93) {
+		return -0.13;
+	} else if (p_stretch < 106) {
+		return 0.0;
+	} else if (p_stretch < 137) {
+		return 0.13;
+	} else if (p_stretch < 144) {
+		return 0.25;
+	} else if (p_stretch < 162) {
+		return 0.37;
+	} else {
+		return 0.5;
+	}
+}
+
+Vector<String> OS_AppleEmbedded::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const {
+	Vector<String> ret;
+	String font_name = _get_default_fontname(p_font_name);
+
+	CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
+	CTFontSymbolicTraits traits = 0;
+	if (p_weight >= 700) {
+		traits |= kCTFontBoldTrait;
+	}
+	if (p_italic) {
+		traits |= kCTFontItalicTrait;
+	}
+	if (p_stretch < 100) {
+		traits |= kCTFontCondensedTrait;
+	} else if (p_stretch > 100) {
+		traits |= kCTFontExpandedTrait;
+	}
+
+	CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
+	CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+	CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
+
+	CGFloat weight = _weight_to_ct(p_weight);
+	CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
+	CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
+
+	CGFloat stretch = _stretch_to_ct(p_stretch);
+	CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
+	CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
+
+	CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+	CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
+	CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
+
+	CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
+	if (font) {
+		CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
+		if (family) {
+			CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
+			CFRange range = CFRangeMake(0, CFStringGetLength(string));
+			CTFontRef fallback_family = CTFontCreateForString(family, string, range);
+			if (fallback_family) {
+				CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
+				if (fallback_font) {
+					CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
+					if (url) {
+						NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
+						ret.push_back(String::utf8([font_path UTF8String]));
+						CFRelease(url);
+					}
+					CFRelease(fallback_font);
+				}
+				CFRelease(fallback_family);
+			}
+			CFRelease(string);
+			CFRelease(family);
+		}
+		CFRelease(font);
+	}
+
+	CFRelease(attributes);
+	CFRelease(traits_dict);
+	CFRelease(sym_traits);
+	CFRelease(font_stretch);
+	CFRelease(font_weight);
+	CFRelease(name);
+
+	return ret;
+}
+
+String OS_AppleEmbedded::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
+	String ret;
+	String font_name = _get_default_fontname(p_font_name);
+
+	CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
+
+	CTFontSymbolicTraits traits = 0;
+	if (p_weight >= 700) {
+		traits |= kCTFontBoldTrait;
+	}
+	if (p_italic) {
+		traits |= kCTFontItalicTrait;
+	}
+	if (p_stretch < 100) {
+		traits |= kCTFontCondensedTrait;
+	} else if (p_stretch > 100) {
+		traits |= kCTFontExpandedTrait;
+	}
+
+	CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
+	CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+	CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
+
+	CGFloat weight = _weight_to_ct(p_weight);
+	CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
+	CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
+
+	CGFloat stretch = _stretch_to_ct(p_stretch);
+	CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
+	CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
+
+	CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+	CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
+	CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
+
+	CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
+	if (font) {
+		CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
+		if (url) {
+			NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
+			ret = String::utf8([font_path UTF8String]);
+			CFRelease(url);
+		}
+		CFRelease(font);
+	}
+
+	CFRelease(attributes);
+	CFRelease(traits_dict);
+	CFRelease(sym_traits);
+	CFRelease(font_stretch);
+	CFRelease(font_weight);
+	CFRelease(name);
+
+	return ret;
+}
+
+void OS_AppleEmbedded::vibrate_handheld(int p_duration_ms, float p_amplitude) {
+	if (apple_embedded->supports_haptic_engine()) {
+		if (p_amplitude > 0.0) {
+			p_amplitude = CLAMP(p_amplitude, 0.0, 1.0);
+		}
+
+		apple_embedded->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude);
+	} else {
+		// iOS <13 does not support duration for vibration
+		AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+	}
+}
+
+bool OS_AppleEmbedded::_check_internal_feature_support(const String &p_feature) {
+	if (p_feature == "system_fonts") {
+		return true;
+	}
+	if (p_feature == "mobile") {
+		return true;
+	}
+
+	return false;
+}
+
+void OS_AppleEmbedded::on_focus_out() {
+	if (is_focused) {
+		is_focused = false;
+
+		if (DisplayServerAppleEmbedded::get_singleton()) {
+			DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+		}
+
+		if (OS::get_singleton()->get_main_loop()) {
+			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+		}
+
+		[GDTAppDelegateService.viewController.godotView stopRendering];
+
+		audio_driver.stop();
+	}
+}
+
+void OS_AppleEmbedded::on_focus_in() {
+	if (!is_focused) {
+		is_focused = true;
+
+		if (DisplayServerAppleEmbedded::get_singleton()) {
+			DisplayServerAppleEmbedded::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+		}
+
+		if (OS::get_singleton()->get_main_loop()) {
+			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+		}
+
+		[GDTAppDelegateService.viewController.godotView startRendering];
+
+		audio_driver.start();
+	}
+}
+
+void OS_AppleEmbedded::on_enter_background() {
+	// Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive.
+
+	if (OS::get_singleton()->get_main_loop()) {
+		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
+	}
+
+	on_focus_out();
+}
+
+void OS_AppleEmbedded::on_exit_background() {
+	if (!is_focused) {
+		on_focus_in();
+
+		if (OS::get_singleton()->get_main_loop()) {
+			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
+		}
+	}
+}
+
+Rect2 OS_AppleEmbedded::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const {
+	String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode");
+
+	if (scalemodestr == "scaleAspectFit") {
+		return fit_keep_aspect_centered(p_window_size, p_imgrect_size);
+	} else if (scalemodestr == "scaleAspectFill") {
+		return fit_keep_aspect_covered(p_window_size, p_imgrect_size);
+	} else if (scalemodestr == "scaleToFill") {
+		return Rect2(Point2(), p_window_size);
+	} else if (scalemodestr == "center") {
+		return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
+	} else {
+		WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr));
+		return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
+	}
+}
+
+#endif // APPLE_EMBEDDED_ENABLED

+ 44 - 0
drivers/apple_embedded/platform_config.h

@@ -0,0 +1,44 @@
+/**************************************************************************/
+/*  platform_config.h                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include <alloca.h>
+
+#define PLATFORM_THREAD_OVERRIDE
+
+#define PTHREAD_RENAME_SELF
+
+#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
+#define _strongify(var)                                      \
+	_Pragma("clang diagnostic push")                         \
+			_Pragma("clang diagnostic ignored \"-Wshadow\"") \
+					__strong typeof(var) var = GDWeak_##var; \
+	_Pragma("clang diagnostic pop")

+ 4 - 4
platform/ios/rendering_context_driver_vulkan_ios.h → drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  rendering_context_driver_vulkan_ios.h                                 */
+/*  rendering_context_driver_vulkan_apple_embedded.h                      */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -36,7 +36,7 @@
 
 #import <QuartzCore/CAMetalLayer.h>
 
-class RenderingContextDriverVulkanIOS : public RenderingContextDriverVulkan {
+class RenderingContextDriverVulkanAppleEmbedded : public RenderingContextDriverVulkan {
 private:
 	virtual const char *_get_platform_surface_extension() const override final;
 
@@ -48,8 +48,8 @@ public:
 		CAMetalLayer *const *layer_ptr;
 	};
 
-	RenderingContextDriverVulkanIOS();
-	~RenderingContextDriverVulkanIOS();
+	RenderingContextDriverVulkanAppleEmbedded();
+	~RenderingContextDriverVulkanAppleEmbedded();
 };
 
 #endif // VULKAN_ENABLED

+ 6 - 6
platform/ios/rendering_context_driver_vulkan_ios.mm → drivers/apple_embedded/rendering_context_driver_vulkan_apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  rendering_context_driver_vulkan_ios.mm                                */
+/*  rendering_context_driver_vulkan_apple_embedded.mm                     */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,7 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "rendering_context_driver_vulkan_ios.h"
+#import "rendering_context_driver_vulkan_apple_embedded.h"
 
 #ifdef VULKAN_ENABLED
 
@@ -38,11 +38,11 @@
 #include <vulkan/vulkan_metal.h>
 #endif
 
-const char *RenderingContextDriverVulkanIOS::_get_platform_surface_extension() const {
+const char *RenderingContextDriverVulkanAppleEmbedded::_get_platform_surface_extension() const {
 	return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
 }
 
-RenderingContextDriver::SurfaceID RenderingContextDriverVulkanIOS::surface_create(const void *p_platform_data) {
+RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAppleEmbedded::surface_create(const void *p_platform_data) {
 	const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
 
 	VkMetalSurfaceCreateInfoEXT create_info = {};
@@ -58,11 +58,11 @@ RenderingContextDriver::SurfaceID RenderingContextDriverVulkanIOS::surface_creat
 	return SurfaceID(surface);
 }
 
-RenderingContextDriverVulkanIOS::RenderingContextDriverVulkanIOS() {
+RenderingContextDriverVulkanAppleEmbedded::RenderingContextDriverVulkanAppleEmbedded() {
 	// Does nothing.
 }
 
-RenderingContextDriverVulkanIOS::~RenderingContextDriverVulkanIOS() {
+RenderingContextDriverVulkanAppleEmbedded::~RenderingContextDriverVulkanAppleEmbedded() {
 	// Does nothing.
 }
 

+ 4 - 4
platform/ios/ios_terminal_logger.h → drivers/apple_embedded/terminal_logger_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  ios_terminal_logger.h                                                 */
+/*  terminal_logger_apple_embedded.h                                      */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -30,13 +30,13 @@
 
 #pragma once
 
-#ifdef IOS_ENABLED
+#ifdef APPLE_EMBEDDED_ENABLED
 
 #include "core/io/logger.h"
 
-class IOSTerminalLogger : public StdLogger {
+class TerminalLoggerAppleEmbedded : public StdLogger {
 public:
 	virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces = {}) override;
 };
 
-#endif // IOS_ENABLED
+#endif // APPLE_EMBEDDED_ENABLED

+ 5 - 5
platform/ios/ios_terminal_logger.mm → drivers/apple_embedded/terminal_logger_apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  ios_terminal_logger.mm                                                */
+/*  terminal_logger_apple_embedded.mm                                     */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,13 +28,13 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "ios_terminal_logger.h"
+#import "terminal_logger_apple_embedded.h"
 
-#ifdef IOS_ENABLED
+#ifdef APPLE_EMBEDDED_ENABLED
 
 #import <os/log.h>
 
-void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
+void TerminalLoggerAppleEmbedded::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
 	if (!should_log(true)) {
 		return;
 	}
@@ -77,4 +77,4 @@ void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, in
 	}
 }
 
-#endif // IOS_ENABLED
+#endif // APPLE_EMBEDDED_ENABLED

+ 2 - 2
platform/ios/tts_ios.h → drivers/apple_embedded/tts_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  tts_ios.h                                                             */
+/*  tts_apple_embedded.h                                                  */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -42,7 +42,7 @@
 #import <AVFoundation/AVFoundation.h>
 #endif
 
-@interface TTS_IOS : NSObject <AVSpeechSynthesizerDelegate> {
+@interface GDTTTS : NSObject <AVSpeechSynthesizerDelegate> {
 	bool speaking;
 	HashMap<id, int> ids;
 

+ 3 - 3
platform/ios/tts_ios.mm → drivers/apple_embedded/tts_apple_embedded.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  tts_ios.mm                                                            */
+/*  tts_apple_embedded.mm                                                 */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,9 +28,9 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "tts_ios.h"
+#import "tts_apple_embedded.h"
 
-@implementation TTS_IOS
+@implementation GDTTTS
 
 - (id)init {
 	self = [super init];

+ 5 - 6
platform/ios/view_controller.h → drivers/apple_embedded/view_controller.h

@@ -32,13 +32,12 @@
 
 #import <UIKit/UIKit.h>
 
-@class GodotView;
-@class GodotNativeVideoView;
-@class GodotKeyboardInputView;
+@class GDTView;
+@class GDTKeyboardInputView;
 
-@interface ViewController : UIViewController
+@interface GDTViewController : UIViewController
 
-@property(nonatomic, readonly, strong) GodotView *godotView;
-@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
+@property(nonatomic, readonly, strong) GDTView *godotView;
+@property(nonatomic, readonly, strong) GDTKeyboardInputView *keyboardView;
 
 @end

+ 31 - 31
platform/ios/view_controller.mm → drivers/apple_embedded/view_controller.mm

@@ -30,44 +30,44 @@
 
 #import "view_controller.h"
 
-#import "display_server_ios.h"
-#import "godot_view.h"
+#import "display_server_apple_embedded.h"
+#import "godot_view_apple_embedded.h"
 #import "godot_view_renderer.h"
-#import "key_mapping_ios.h"
+#import "key_mapping_apple_embedded.h"
 #import "keyboard_input_view.h"
-#import "os_ios.h"
+#import "os_apple_embedded.h"
 
 #include "core/config/project_settings.h"
 
 #import <AVFoundation/AVFoundation.h>
 #import <GameController/GameController.h>
 
-@interface ViewController () <GodotViewDelegate>
+@interface GDTViewController () <GDTViewDelegate>
 
-@property(strong, nonatomic) GodotViewRenderer *renderer;
-@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
+@property(strong, nonatomic) GDTViewRenderer *renderer;
+@property(strong, nonatomic) GDTKeyboardInputView *keyboardView;
 
 @property(strong, nonatomic) UIView *godotLoadingOverlay;
 
 @end
 
-@implementation ViewController
+@implementation GDTViewController
 
-- (GodotView *)godotView {
-	return (GodotView *)self.view;
+- (GDTView *)godotView {
+	return (GDTView *)self.view;
 }
 
 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
 	[super pressesBegan:presses withEvent:event];
 
-	if (!DisplayServerIOS::get_singleton() || DisplayServerIOS::get_singleton()->is_keyboard_active()) {
+	if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
 		return;
 	}
 	if (@available(iOS 13.4, *)) {
 		for (UIPress *press in presses) {
 			String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
 			String u32text = String::utf8([press.key.characters UTF8String]);
-			Key key = KeyMappingIOS::remap_key(press.key.keyCode);
+			Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
 
 			if (press.key.keyCode == 0 && u32text.is_empty() && u32lbl.is_empty()) {
 				continue;
@@ -78,15 +78,15 @@
 				us = u32lbl[0];
 			}
 
-			KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+			KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
 
 			if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
 				for (int i = 0; i < u32text.length(); i++) {
 					const char32_t c = u32text[i];
-					DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
+					DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
 				}
 			} else {
-				DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
+				DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
 			}
 		}
 	}
@@ -95,13 +95,13 @@
 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
 	[super pressesEnded:presses withEvent:event];
 
-	if (!DisplayServerIOS::get_singleton() || DisplayServerIOS::get_singleton()->is_keyboard_active()) {
+	if (!DisplayServerAppleEmbedded::get_singleton() || DisplayServerAppleEmbedded::get_singleton()->is_keyboard_active()) {
 		return;
 	}
 	if (@available(iOS 13.4, *)) {
 		for (UIPress *press in presses) {
 			String u32lbl = String::utf8([press.key.charactersIgnoringModifiers UTF8String]);
-			Key key = KeyMappingIOS::remap_key(press.key.keyCode);
+			Key key = KeyMappingAppleEmbedded::remap_key(press.key.keyCode);
 
 			if (press.key.keyCode == 0 && u32lbl.is_empty()) {
 				continue;
@@ -112,16 +112,16 @@
 				us = u32lbl[0];
 			}
 
-			KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+			KeyLocation location = KeyMappingAppleEmbedded::key_location(press.key.keyCode);
 
-			DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
+			DisplayServerAppleEmbedded::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
 		}
 	}
 }
 
 - (void)loadView {
-	GodotView *view = [[GodotView alloc] init];
-	GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
+	GDTView *view = GDTViewCreate();
+	GDTViewRenderer *renderer = [[GDTViewRenderer alloc] init];
 
 	self.renderer = renderer;
 	self.view = view;
@@ -170,7 +170,7 @@
 
 - (void)observeKeyboard {
 	print_verbose("Setting up keyboard input view.");
-	self.keyboardView = [GodotKeyboardInputView new];
+	self.keyboardView = [GDTKeyboardInputView new];
 	[self.view addSubview:self.keyboardView];
 
 	print_verbose("Adding observer for keyboard show/hide.");
@@ -204,7 +204,7 @@
 	[self.view addSubview:self.godotLoadingOverlay];
 }
 
-- (BOOL)godotViewFinishedSetup:(GodotView *)view {
+- (BOOL)godotViewFinishedSetup:(GDTView *)view {
 	[self.godotLoadingOverlay removeFromSuperview];
 	self.godotLoadingOverlay = nil;
 
@@ -235,11 +235,11 @@
 }
 
 - (BOOL)shouldAutorotate {
-	if (!DisplayServerIOS::get_singleton()) {
+	if (!DisplayServerAppleEmbedded::get_singleton()) {
 		return NO;
 	}
 
-	switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+	switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
 		case DisplayServer::SCREEN_SENSOR:
 		case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
 		case DisplayServer::SCREEN_SENSOR_PORTRAIT:
@@ -250,11 +250,11 @@
 }
 
 - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
-	if (!DisplayServerIOS::get_singleton()) {
+	if (!DisplayServerAppleEmbedded::get_singleton()) {
 		return UIInterfaceOrientationMaskAll;
 	}
 
-	switch (DisplayServerIOS::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+	switch (DisplayServerAppleEmbedded::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
 		case DisplayServer::SCREEN_PORTRAIT:
 			return UIInterfaceOrientationMaskPortrait;
 		case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
@@ -305,14 +305,14 @@
 	CGRect rawFrame = [value CGRectValue];
 	CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
 
-	if (DisplayServerIOS::get_singleton()) {
-		DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+	if (DisplayServerAppleEmbedded::get_singleton()) {
+		DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
 	}
 }
 
 - (void)keyboardHidden:(NSNotification *)notification {
-	if (DisplayServerIOS::get_singleton()) {
-		DisplayServerIOS::get_singleton()->virtual_keyboard_set_height(0);
+	if (DisplayServerAppleEmbedded::get_singleton()) {
+		DisplayServerAppleEmbedded::get_singleton()->virtual_keyboard_set_height(0);
 	}
 }
 

+ 0 - 1
drivers/coremidi/midi_driver_coremidi.mm

@@ -34,7 +34,6 @@
 
 #include "core/string/print_string.h"
 
-#import <CoreAudio/HostTime.h>
 #import <CoreServices/CoreServices.h>
 
 Mutex MIDIDriverCoreMidi::mutex;

+ 3 - 0
drivers/metal/SCsub

@@ -39,6 +39,9 @@ if "-std=gnu++17" in env_metal["CXXFLAGS"]:
     env_metal["CXXFLAGS"].remove("-std=gnu++17")
 env_metal.Append(CXXFLAGS=["-std=c++20"])
 
+# Enable module support
+env_metal.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+
 # Driver source files
 
 driver_obj = []

+ 2 - 2
drivers/metal/rendering_device_driver_metal.mm

@@ -4184,8 +4184,8 @@ Error RenderingDeviceDriverMetal::initialize(uint32_t p_device_index, uint32_t p
 			error_string += "- No support for image cube arrays.\n";
 		}
 
-#if defined(IOS_ENABLED)
-		// iOS platform ports currently don't exit themselves when this method returns `ERR_CANT_CREATE`.
+#if defined(APPLE_EMBEDDED_ENABLED)
+		// Apple Embedded platforms exports currently don't exit themselves when this method returns `ERR_CANT_CREATE`.
 		OS::get_singleton()->alert(error_string + "\nClick OK to exit (black screen will be visible).");
 #else
 		OS::get_singleton()->alert(error_string + "\nClick OK to exit.");

+ 2750 - 0
editor/export/editor_export_platform_apple_embedded.cpp

@@ -0,0 +1,2750 @@
+/**************************************************************************/
+/*  editor_export_platform_apple_embedded.cpp                             */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "editor_export_platform_apple_embedded.h"
+
+#include "core/io/json.h"
+#include "core/io/plist.h"
+#include "core/string/translation.h"
+#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
+#include "editor/editor_string_names.h"
+#include "editor/export/editor_export.h"
+#include "editor/export/lipo.h"
+#include "editor/export/macho.h"
+#include "editor/import/resource_importer_texture_settings.h"
+#include "editor/plugins/script_editor_plugin.h"
+#include "editor/themes/editor_scale.h"
+
+#include "modules/modules_enabled.gen.h" // For mono.
+#include "modules/svg/image_loader_svg.h"
+
+void EditorExportPlatformAppleEmbedded::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {
+	// Vulkan and OpenGL ES 3.0 both mandate ETC2 support.
+	r_features->push_back("etc2");
+	r_features->push_back("astc");
+
+	Vector<String> architectures = _get_preset_architectures(p_preset);
+	for (int i = 0; i < architectures.size(); ++i) {
+		r_features->push_back(architectures[i]);
+	}
+}
+
+Vector<EditorExportPlatformAppleEmbedded::ExportArchitecture> EditorExportPlatformAppleEmbedded::_get_supported_architectures() const {
+	Vector<ExportArchitecture> archs;
+	archs.push_back(ExportArchitecture("arm64", true));
+	return archs;
+}
+
+struct APIAccessInfo {
+	String prop_name;
+	String type_name;
+	Vector<String> prop_flag_value;
+	Vector<String> prop_flag_name;
+	int default_value;
+};
+
+static const APIAccessInfo api_info[] = {
+	{ "file_timestamp",
+			"NSPrivacyAccessedAPICategoryFileTimestamp",
+			{ "DDA9.1", "C617.1", "3B52.1" },
+			{ "Display to user on-device:", "Inside app or group container", "Files provided to app by user" },
+			3 },
+	{ "system_boot_time",
+			"NSPrivacyAccessedAPICategorySystemBootTime",
+			{ "35F9.1", "8FFB.1", "3D61.1" },
+			{ "Measure time on-device", "Calculate absolute event timestamps", "User-initiated bug report" },
+			1 },
+	{ "disk_space",
+			"NSPrivacyAccessedAPICategoryDiskSpace",
+			{ "E174.1", "85F4.1", "7D9E.1", "B728.1" },
+			{ "Write or delete file on-device", "Display to user on-device", "User-initiated bug report", "Health research app" },
+			3 },
+	{ "active_keyboard",
+			"NSPrivacyAccessedAPICategoryActiveKeyboards",
+			{ "3EC4.1", "54BD.1" },
+			{ "Custom keyboard app on-device", "Customize UI on-device:2" },
+			0 },
+	{ "user_defaults",
+			"NSPrivacyAccessedAPICategoryUserDefaults",
+			{ "1C8F.1", "AC6B.1", "CA92.1" },
+			{ "Access info from same App Group", "Access managed app configuration", "Access info from same app" },
+			0 },
+};
+
+struct DataCollectionInfo {
+	String prop_name;
+	String type_name;
+};
+
+static const DataCollectionInfo data_collect_type_info[] = {
+	{ "name", "NSPrivacyCollectedDataTypeName" },
+	{ "email_address", "NSPrivacyCollectedDataTypeEmailAddress" },
+	{ "phone_number", "NSPrivacyCollectedDataTypePhoneNumber" },
+	{ "physical_address", "NSPrivacyCollectedDataTypePhysicalAddress" },
+	{ "other_contact_info", "NSPrivacyCollectedDataTypeOtherUserContactInfo" },
+	{ "health", "NSPrivacyCollectedDataTypeHealth" },
+	{ "fitness", "NSPrivacyCollectedDataTypeFitness" },
+	{ "payment_info", "NSPrivacyCollectedDataTypePaymentInfo" },
+	{ "credit_info", "NSPrivacyCollectedDataTypeCreditInfo" },
+	{ "other_financial_info", "NSPrivacyCollectedDataTypeOtherFinancialInfo" },
+	{ "precise_location", "NSPrivacyCollectedDataTypePreciseLocation" },
+	{ "coarse_location", "NSPrivacyCollectedDataTypeCoarseLocation" },
+	{ "sensitive_info", "NSPrivacyCollectedDataTypeSensitiveInfo" },
+	{ "contacts", "NSPrivacyCollectedDataTypeContacts" },
+	{ "emails_or_text_messages", "NSPrivacyCollectedDataTypeEmailsOrTextMessages" },
+	{ "photos_or_videos", "NSPrivacyCollectedDataTypePhotosorVideos" },
+	{ "audio_data", "NSPrivacyCollectedDataTypeAudioData" },
+	{ "gameplay_content", "NSPrivacyCollectedDataTypeGameplayContent" },
+	{ "customer_support", "NSPrivacyCollectedDataTypeCustomerSupport" },
+	{ "other_user_content", "NSPrivacyCollectedDataTypeOtherUserContent" },
+	{ "browsing_history", "NSPrivacyCollectedDataTypeBrowsingHistory" },
+	{ "search_hhistory", "NSPrivacyCollectedDataTypeSearchHistory" },
+	{ "user_id", "NSPrivacyCollectedDataTypeUserID" },
+	{ "device_id", "NSPrivacyCollectedDataTypeDeviceID" },
+	{ "purchase_history", "NSPrivacyCollectedDataTypePurchaseHistory" },
+	{ "product_interaction", "NSPrivacyCollectedDataTypeProductInteraction" },
+	{ "advertising_data", "NSPrivacyCollectedDataTypeAdvertisingData" },
+	{ "other_usage_data", "NSPrivacyCollectedDataTypeOtherUsageData" },
+	{ "crash_data", "NSPrivacyCollectedDataTypeCrashData" },
+	{ "performance_data", "NSPrivacyCollectedDataTypePerformanceData" },
+	{ "other_diagnostic_data", "NSPrivacyCollectedDataTypeOtherDiagnosticData" },
+	{ "environment_scanning", "NSPrivacyCollectedDataTypeEnvironmentScanning" },
+	{ "hands", "NSPrivacyCollectedDataTypeHands" },
+	{ "head", "NSPrivacyCollectedDataTypeHead" },
+	{ "other_data_types", "NSPrivacyCollectedDataTypeOtherDataTypes" },
+};
+
+static const DataCollectionInfo data_collect_purpose_info[] = {
+	{ "Analytics", "NSPrivacyCollectedDataTypePurposeAnalytics" },
+	{ "App Functionality", "NSPrivacyCollectedDataTypePurposeAppFunctionality" },
+	{ "Developer Advertising", "NSPrivacyCollectedDataTypePurposeDeveloperAdvertising" },
+	{ "Third-party Advertising", "NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising" },
+	{ "Product Personalization", "NSPrivacyCollectedDataTypePurposeProductPersonalization" },
+	{ "Other", "NSPrivacyCollectedDataTypePurposeOther" },
+};
+
+static const String export_method_string[] = {
+	"app-store",
+	"development",
+	"ad-hoc",
+	"enterprise",
+};
+
+String EditorExportPlatformAppleEmbedded::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
+	if (p_preset) {
+		if (p_name == "application/app_store_team_id") {
+			String team_id = p_preset->get("application/app_store_team_id");
+			if (team_id.is_empty()) {
+				return TTR("App Store Team ID not specified.") + "\n";
+			}
+		} else if (p_name == "application/bundle_identifier") {
+			String identifier = p_preset->get("application/bundle_identifier");
+			String pn_err;
+			if (!is_package_name_valid(identifier, &pn_err)) {
+				return TTR("Invalid Identifier:") + " " + pn_err;
+			}
+		} else if (p_name == "privacy/file_timestamp_access_reasons") {
+			int access = p_preset->get("privacy/file_timestamp_access_reasons");
+			if (access == 0) {
+				return TTR("At least one file timestamp access reason should be selected.");
+			}
+		} else if (p_name == "privacy/disk_space_access_reasons") {
+			int access = p_preset->get("privacy/disk_space_access_reasons");
+			if (access == 0) {
+				return TTR("At least one disk space access reason should be selected.");
+			}
+		} else if (p_name == "privacy/system_boot_time_access_reasons") {
+			int access = p_preset->get("privacy/system_boot_time_access_reasons");
+			if (access == 0) {
+				return TTR("At least one system boot time access reason should be selected.");
+			}
+		}
+	}
+	return String();
+}
+
+void EditorExportPlatformAppleEmbedded::_notification(int p_what) {
+#ifdef MACOS_ENABLED
+	if (p_what == NOTIFICATION_POSTINITIALIZE) {
+		if (EditorExport::get_singleton()) {
+			EditorExport::get_singleton()->connect_presets_runnable_updated(callable_mp(this, &EditorExportPlatformAppleEmbedded::_update_preset_status));
+		}
+	}
+#endif
+}
+
+bool EditorExportPlatformAppleEmbedded::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+	// Hide unsupported .NET embedding option.
+	if (p_option == "dotnet/embed_build_outputs") {
+		return false;
+	}
+
+	if (p_preset == nullptr) {
+		return true;
+	}
+
+	bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
+	if (p_option.begins_with("privacy") ||
+			(p_option.begins_with("icons/") && !p_option.begins_with("icons/icon") && !p_option.begins_with("icons/app_store")) ||
+			p_option == "custom_template/debug" ||
+			p_option == "custom_template/release" ||
+			p_option == "application/additional_plist_content" ||
+			p_option == "application/delete_old_export_files_unconditionally" ||
+			p_option == "application/icon_interpolation" ||
+			p_option == "application/signature") {
+		return advanced_options_enabled;
+	}
+
+	return true;
+}
+
+void EditorExportPlatformAppleEmbedded::get_export_options(List<ExportOption> *r_options) const {
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+
+	Vector<ExportArchitecture> architectures = _get_supported_architectures();
+	for (int i = 0; i < architectures.size(); ++i) {
+		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("architectures"), architectures[i].name)), architectures[i].is_default));
+	}
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple Development"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple Distribution"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_release", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), ""));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/delete_old_export_files_unconditionally"), false));
+
+	Vector<PluginConfigAppleEmbedded> found_plugins = get_plugins(get_platform_name());
+	for (int i = 0; i < found_plugins.size(); i++) {
+		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false));
+	}
+
+	HashSet<String> plist_keys;
+
+	for (int i = 0; i < found_plugins.size(); i++) {
+		// Editable plugin plist values
+		PluginConfigAppleEmbedded plugin = found_plugins[i];
+
+		for (const KeyValue<String, PluginConfigAppleEmbedded::PlistItem> &E : plugin.plist) {
+			switch (E.value.type) {
+				case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: {
+					String preset_name = "plugins_plist/" + E.key;
+					if (!plist_keys.has(preset_name)) {
+						r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), E.value.value));
+						plist_keys.insert(preset_name);
+					}
+				} break;
+				default:
+					continue;
+			}
+		}
+	}
+
+	plugins_changed.clear();
+	plugins = found_plugins;
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/increased_memory_limit"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "entitlements/game_center"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/push_notifications", PROPERTY_HINT_ENUM, "Disabled,Production,Development"), "Disabled"));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "entitlements/additional", PROPERTY_HINT_MULTILINE_TEXT), ""));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "capabilities/additional"), PackedStringArray()));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false));
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the camera"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/camera_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need access to the photo library"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/photolibrary_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
+
+	for (uint64_t i = 0; i < std::size(api_info); ++i) {
+		String prop_name = vformat("privacy/%s_access_reasons", api_info[i].prop_name);
+		String hint;
+		for (int j = 0; j < api_info[i].prop_flag_value.size(); j++) {
+			if (j != 0) {
+				hint += ",";
+			}
+			hint += vformat("%s - %s:%d", api_info[i].prop_flag_value[j], api_info[i].prop_flag_name[j], (1 << j));
+		}
+		r_options->push_back(ExportOption(PropertyInfo(Variant::INT, prop_name, PROPERTY_HINT_FLAGS, hint), api_info[i].default_value));
+	}
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "privacy/tracking_enabled"), false));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "privacy/tracking_domains"), Vector<String>()));
+
+	{
+		String hint;
+		for (uint64_t i = 0; i < std::size(data_collect_purpose_info); ++i) {
+			if (i != 0) {
+				hint += ",";
+			}
+			hint += vformat("%s:%d", data_collect_purpose_info[i].prop_name, (1 << i));
+		}
+		for (uint64_t i = 0; i < std::size(data_collect_type_info); ++i) {
+			r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/collected", data_collect_type_info[i].prop_name)), false));
+			r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[i].prop_name)), false));
+			r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[i].prop_name)), false));
+			r_options->push_back(ExportOption(PropertyInfo(Variant::INT, vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[i].prop_name), PROPERTY_HINT_FLAGS, hint), 0));
+		}
+	}
+
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_dark", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_tinted", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+
+	HashSet<String> used_names;
+
+	Vector<IconInfo> icon_infos = get_icon_infos();
+	for (int i = 0; i < icon_infos.size(); ++i) {
+		if (!used_names.has(icon_infos[i].preset_key)) {
+			used_names.insert(icon_infos[i].preset_key);
+			r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key), PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+			r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_dark", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+			r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_tinted", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+		}
+	}
+}
+
+void EditorExportPlatformAppleEmbedded::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const AppleEmbeddedConfigData &p_config, bool p_debug) {
+	String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "Apple Development" : p_preset->get("application/code_sign_identity_debug");
+	String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "Apple Distribution" : p_preset->get("application/code_sign_identity_release");
+	bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "Apple Development" && dbg_sign_id != "Apple Distribution");
+	bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "Apple Development" && rel_sign_id != "Apple Distribution");
+
+	String provisioning_profile_specifier_dbg = p_preset->get_or_env("application/provisioning_profile_specifier_debug", ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG).operator String();
+	bool valid_dbg_specifier = !provisioning_profile_specifier_dbg.is_empty();
+	dbg_manual |= valid_dbg_specifier;
+
+	String provisioning_profile_specifier_rel = p_preset->get_or_env("application/provisioning_profile_specifier_release", ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE).operator String();
+	bool valid_rel_specifier = !provisioning_profile_specifier_rel.is_empty();
+	rel_manual |= valid_rel_specifier;
+
+	String str = String::utf8((const char *)pfile.ptr(), pfile.size());
+	String strnew;
+	Vector<String> lines = str.split("\n");
+	for (int i = 0; i < lines.size(); i++) {
+		if (lines[i].contains("$binary")) {
+			strnew += lines[i].replace("$binary", p_config.binary_name) + "\n";
+		} else if (lines[i].contains("$modules_buildfile")) {
+			strnew += lines[i].replace("$modules_buildfile", p_config.modules_buildfile) + "\n";
+		} else if (lines[i].contains("$modules_fileref")) {
+			strnew += lines[i].replace("$modules_fileref", p_config.modules_fileref) + "\n";
+		} else if (lines[i].contains("$modules_buildphase")) {
+			strnew += lines[i].replace("$modules_buildphase", p_config.modules_buildphase) + "\n";
+		} else if (lines[i].contains("$modules_buildgrp")) {
+			strnew += lines[i].replace("$modules_buildgrp", p_config.modules_buildgrp) + "\n";
+		} else if (lines[i].contains("$name")) {
+			strnew += lines[i].replace("$name", p_config.pkg_name) + "\n";
+		} else if (lines[i].contains("$bundle_identifier")) {
+			strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n";
+		} else if (lines[i].contains("$short_version")) {
+			strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n";
+		} else if (lines[i].contains("$version")) {
+			strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n";
+		} else if (lines[i].contains("$min_version")) {
+			strnew += lines[i].replace("$min_version",
+							  p_preset->get("application/min_" + get_platform_name() + "_version")) +
+					"\n";
+		} else if (lines[i].contains("$signature")) {
+			strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
+		} else if (lines[i].contains("$team_id")) {
+			strnew += lines[i].replace("$team_id", p_preset->get("application/app_store_team_id")) + "\n";
+		} else if (lines[i].contains("$default_build_config")) {
+			strnew += lines[i].replace("$default_build_config", p_debug ? "Debug" : "Release") + "\n";
+		} else if (lines[i].contains("$export_method")) {
+			int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release");
+			strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n";
+		} else if (lines[i].contains("$provisioning_profile_specifier_debug")) {
+			strnew += lines[i].replace("$provisioning_profile_specifier_debug", provisioning_profile_specifier_dbg) + "\n";
+		} else if (lines[i].contains("$provisioning_profile_specifier_release")) {
+			strnew += lines[i].replace("$provisioning_profile_specifier_release", provisioning_profile_specifier_rel) + "\n";
+		} else if (lines[i].contains("$provisioning_profile_specifier")) {
+			String specifier = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel;
+			strnew += lines[i].replace("$provisioning_profile_specifier", specifier) + "\n";
+		} else if (lines[i].contains("$provisioning_profile_uuid_release")) {
+			strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE)) + "\n";
+		} else if (lines[i].contains("$provisioning_profile_uuid_debug")) {
+			strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG)) + "\n";
+		} else if (lines[i].contains("$code_sign_style_debug")) {
+			if (dbg_manual) {
+				strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n";
+			} else {
+				strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n";
+			}
+		} else if (lines[i].contains("$code_sign_style_release")) {
+			if (rel_manual) {
+				strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n";
+			} else {
+				strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n";
+			}
+		} else if (lines[i].contains("$provisioning_profile_uuid")) {
+			String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE);
+			if (uuid.is_empty()) {
+				Variant variant = p_debug ? provisioning_profile_specifier_dbg : provisioning_profile_specifier_rel;
+				bool valid = p_debug ? valid_dbg_specifier : valid_rel_specifier;
+				uuid = valid ? variant : "";
+			}
+			strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n";
+		} else if (lines[i].contains("$code_sign_identity_debug")) {
+			strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n";
+		} else if (lines[i].contains("$code_sign_identity_release")) {
+			strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n";
+		} else if (lines[i].contains("$additional_plist_content")) {
+			strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n";
+		} else if (lines[i].contains("$godot_archs")) {
+			strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
+		} else if (lines[i].contains("$linker_flags")) {
+			strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
+		} else if (lines[i].contains("$targeted_device_family")) {
+			String xcode_value;
+			switch ((int)p_preset->get("application/targeted_device_family")) {
+				case 0: // iPhone
+					xcode_value = "1";
+					break;
+				case 1: // iPad
+					xcode_value = "2";
+					break;
+				case 2: // iPhone & iPad
+					xcode_value = "1,2";
+					break;
+			}
+			strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n";
+		} else if (lines[i].contains("$cpp_code")) {
+			strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
+		} else if (lines[i].contains("$docs_in_place")) {
+			strnew += lines[i].replace("$docs_in_place", ((bool)p_preset->get("user_data/accessible_from_files_app")) ? "<true/>" : "<false/>") + "\n";
+		} else if (lines[i].contains("$docs_sharing")) {
+			strnew += lines[i].replace("$docs_sharing", ((bool)p_preset->get("user_data/accessible_from_itunes_sharing")) ? "<true/>" : "<false/>") + "\n";
+		} else if (lines[i].contains("$entitlements_full")) {
+			String entitlements;
+			if ((String)p_preset->get("entitlements/push_notifications") != "Disabled") {
+				entitlements += "<key>aps-environment</key>\n<string>" + p_preset->get("entitlements/push_notifications").operator String().to_lower() + "</string>" + "\n";
+			}
+			if ((bool)p_preset->get("entitlements/game_center")) {
+				entitlements += "<key>com.apple.developer.game-center</key>\n<true/>\n";
+			}
+			if ((bool)p_preset->get("entitlements/increased_memory_limit")) {
+				entitlements += "<key>com.apple.developer.kernel.increased-memory-limit</key>\n<true/>\n";
+			}
+			entitlements += p_preset->get("entitlements/additional").operator String() + "\n";
+
+			strnew += lines[i].replace("$entitlements_full", entitlements);
+		} else if (lines[i].contains("$required_device_capabilities")) {
+			String capabilities;
+
+			// I've removed armv7 as we can run on 64bit only devices
+			// Note that capabilities listed here are requirements for the app to be installed.
+			// They don't enable anything.
+			Vector<String> capabilities_list = p_config.capabilities;
+
+			if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) {
+				capabilities_list.push_back("wifi");
+			}
+			if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) {
+				capabilities_list.push_back("iphone-performance-gaming-tier");
+			}
+			if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) {
+				capabilities_list.push_back("iphone-ipad-minimum-performance-a12");
+			}
+			for (int idx = 0; idx < capabilities_list.size(); idx++) {
+				capabilities += "<string>" + capabilities_list[idx] + "</string>\n";
+			}
+			for (const String &cap : p_preset->get("capabilities/additional").operator PackedStringArray()) {
+				capabilities += "<string>" + cap + "</string>\n";
+			}
+
+			strnew += lines[i].replace("$required_device_capabilities", capabilities);
+		} else if (lines[i].contains("$interface_orientations")) {
+			String orientations;
+			const DisplayServer::ScreenOrientation screen_orientation =
+					DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation")));
+
+			switch (screen_orientation) {
+				case DisplayServer::SCREEN_LANDSCAPE:
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					break;
+				case DisplayServer::SCREEN_PORTRAIT:
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					break;
+				case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					break;
+				case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+					// Allow both landscape orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+					// Allow both portrait orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR:
+					// Allow all screen orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+			}
+
+			strnew += lines[i].replace("$interface_orientations", orientations);
+		} else if (lines[i].contains("$ipad_interface_orientations")) {
+			String orientations;
+			const DisplayServer::ScreenOrientation screen_orientation =
+					DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation")));
+
+			switch (screen_orientation) {
+				case DisplayServer::SCREEN_LANDSCAPE:
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					break;
+				case DisplayServer::SCREEN_PORTRAIT:
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					break;
+				case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					break;
+				case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+					// Allow both landscape orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+					// Allow both portrait orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+				case DisplayServer::SCREEN_SENSOR:
+					// Allow all screen orientations depending on sensor direction.
+					orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n";
+					orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortrait</string>\n";
+					orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n";
+					break;
+			}
+
+			strnew += lines[i].replace("$ipad_interface_orientations", orientations);
+		} else if (lines[i].contains("$camera_usage_description")) {
+			String description = p_preset->get("privacy/camera_usage_description");
+			strnew += lines[i].replace("$camera_usage_description", description) + "\n";
+		} else if (lines[i].contains("$microphone_usage_description")) {
+			String description = p_preset->get("privacy/microphone_usage_description");
+			strnew += lines[i].replace("$microphone_usage_description", description) + "\n";
+		} else if (lines[i].contains("$photolibrary_usage_description")) {
+			String description = p_preset->get("privacy/photolibrary_usage_description");
+			strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n";
+		} else if (lines[i].contains("$plist_launch_screen_name")) {
+			String value = "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>";
+			strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n";
+		} else if (lines[i].contains("$pbx_launch_screen_file_reference")) {
+			String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };";
+			strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n";
+		} else if (lines[i].contains("$pbx_launch_screen_copy_files")) {
+			String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,";
+			strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n";
+		} else if (lines[i].contains("$pbx_launch_screen_build_phase")) {
+			String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,";
+			strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n";
+		} else if (lines[i].contains("$pbx_launch_screen_build_reference")) {
+			String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };";
+			strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n";
+#ifndef DISABLE_DEPRECATED
+		} else if (lines[i].contains("$pbx_launch_image_usage_setting")) {
+			strnew += lines[i].replace("$pbx_launch_image_usage_setting", "") + "\n";
+#endif
+		} else if (lines[i].contains("$launch_screen_image_mode")) {
+			int image_scale_mode = p_preset->get("storyboard/image_scale_mode");
+			String value;
+
+			switch (image_scale_mode) {
+				case 0: {
+					String logo_path = get_project_setting(p_preset, "application/boot_splash/image");
+					bool is_on = get_project_setting(p_preset, "application/boot_splash/fullsize");
+					// If custom logo is not specified, Godot does not scale default one, so we should do the same.
+					value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center";
+				} break;
+				default: {
+					value = storyboard_image_scale_mode[image_scale_mode - 1];
+				}
+			}
+
+			strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n";
+		} else if (lines[i].contains("$launch_screen_background_color")) {
+			bool use_custom = p_preset->get("storyboard/use_custom_bg_color");
+			Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : get_project_setting(p_preset, "application/boot_splash/bg_color");
+			const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\"";
+
+			Dictionary value_dictionary;
+			value_dictionary["red"] = color.r;
+			value_dictionary["green"] = color.g;
+			value_dictionary["blue"] = color.b;
+			value_dictionary["alpha"] = color.a;
+			String value = value_format.format(value_dictionary, "$_");
+
+			strnew += lines[i].replace("$launch_screen_background_color", value) + "\n";
+		} else if (lines[i].contains("$pbx_locale_file_reference")) {
+			String locale_files;
+			Vector<String> translations = get_project_setting(p_preset, "internationalization/locale/translations");
+			if (translations.size() > 0) {
+				HashSet<String> languages;
+				for (const String &E : translations) {
+					Ref<Translation> tr = ResourceLoader::load(E);
+					if (tr.is_valid() && tr->get_locale() != "en") {
+						languages.insert(tr->get_locale());
+					}
+				}
+
+				int index = 0;
+				for (const String &lang : languages) {
+					locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = " + lang + "; path = " + lang + ".lproj/InfoPlist.strings; sourceTree = \"<group>\"; };\n";
+					index++;
+				}
+			}
+			strnew += lines[i].replace("$pbx_locale_file_reference", locale_files);
+		} else if (lines[i].contains("$pbx_locale_build_reference")) {
+			String locale_files;
+			Vector<String> translations = get_project_setting(p_preset, "internationalization/locale/translations");
+			if (translations.size() > 0) {
+				HashSet<String> languages;
+				for (const String &E : translations) {
+					Ref<Translation> tr = ResourceLoader::load(E);
+					if (tr.is_valid() && tr->get_locale() != "en") {
+						languages.insert(tr->get_locale());
+					}
+				}
+
+				int index = 0;
+				for (const String &lang : languages) {
+					locale_files += "D0BCFE4518AEBDA2004A" + itos(index).pad_zeros(4) + " /* " + lang + " */,\n";
+					index++;
+				}
+			}
+			strnew += lines[i].replace("$pbx_locale_build_reference", locale_files);
+		} else if (lines[i].contains("$swift_runtime_migration")) {
+			String value = !p_config.use_swift_runtime ? "" : "LastSwiftMigration = 1250;";
+			strnew += lines[i].replace("$swift_runtime_migration", value) + "\n";
+		} else if (lines[i].contains("$swift_runtime_build_settings")) {
+			String value = !p_config.use_swift_runtime ? "" : R"(
+                     CLANG_ENABLE_MODULES = YES;
+                     SWIFT_OBJC_BRIDGING_HEADER = "$binary/dummy.h";
+                     SWIFT_VERSION = 5.0;
+                     )";
+			value = value.replace("$binary", p_config.binary_name);
+			strnew += lines[i].replace("$swift_runtime_build_settings", value) + "\n";
+		} else if (lines[i].contains("$swift_runtime_fileref")) {
+			String value = !p_config.use_swift_runtime ? "" : R"(
+                     90B4C2AA2680BC560039117A /* dummy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "dummy.h"; sourceTree = "<group>"; };
+                     90B4C2B52680C7E90039117A /* dummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "dummy.swift"; sourceTree = "<group>"; };
+                     )";
+			strnew += lines[i].replace("$swift_runtime_fileref", value) + "\n";
+		} else if (lines[i].contains("$swift_runtime_binary_files")) {
+			String value = !p_config.use_swift_runtime ? "" : R"(
+                     90B4C2AA2680BC560039117A /* dummy.h */,
+                     90B4C2B52680C7E90039117A /* dummy.swift */,
+                     )";
+			strnew += lines[i].replace("$swift_runtime_binary_files", value) + "\n";
+		} else if (lines[i].contains("$swift_runtime_buildfile")) {
+			String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B4C2B52680C7E90039117A /* dummy.swift */; };";
+			strnew += lines[i].replace("$swift_runtime_buildfile", value) + "\n";
+		} else if (lines[i].contains("$swift_runtime_build_phase")) {
+			String value = !p_config.use_swift_runtime ? "" : "90B4C2B62680C7E90039117A /* dummy.swift */,";
+			strnew += lines[i].replace("$swift_runtime_build_phase", value) + "\n";
+		} else if (lines[i].contains("$priv_collection")) {
+			bool section_opened = false;
+			for (uint64_t j = 0; j < std::size(data_collect_type_info); ++j) {
+				bool data_collected = p_preset->get(vformat("privacy/collected_data/%s/collected", data_collect_type_info[j].prop_name));
+				bool linked = p_preset->get(vformat("privacy/collected_data/%s/linked_to_user", data_collect_type_info[j].prop_name));
+				bool tracking = p_preset->get(vformat("privacy/collected_data/%s/used_for_tracking", data_collect_type_info[j].prop_name));
+				int purposes = p_preset->get(vformat("privacy/collected_data/%s/collection_purposes", data_collect_type_info[j].prop_name));
+				if (data_collected) {
+					if (!section_opened) {
+						section_opened = true;
+						strnew += "\t<key>NSPrivacyCollectedDataTypes</key>\n";
+						strnew += "\t<array>\n";
+					}
+					strnew += "\t\t<dict>\n";
+					strnew += "\t\t\t<key>NSPrivacyCollectedDataType</key>\n";
+					strnew += vformat("\t\t\t<string>%s</string>\n", data_collect_type_info[j].type_name);
+					strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypeLinked</key>\n";
+					if (linked) {
+						strnew += "\t\t\t\t<true/>\n";
+					} else {
+						strnew += "\t\t\t\t<false/>\n";
+					}
+					strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypeTracking</key>\n";
+					if (tracking) {
+						strnew += "\t\t\t\t<true/>\n";
+					} else {
+						strnew += "\t\t\t\t<false/>\n";
+					}
+					if (purposes != 0) {
+						strnew += "\t\t\t\t<key>NSPrivacyCollectedDataTypePurposes</key>\n";
+						strnew += "\t\t\t\t<array>\n";
+						for (uint64_t k = 0; k < std::size(data_collect_purpose_info); ++k) {
+							if (purposes & (1 << k)) {
+								strnew += vformat("\t\t\t\t\t<string>%s</string>\n", data_collect_purpose_info[k].type_name);
+							}
+						}
+						strnew += "\t\t\t\t</array>\n";
+					}
+					strnew += "\t\t\t</dict>\n";
+				}
+			}
+			if (section_opened) {
+				strnew += "\t</array>\n";
+			}
+		} else if (lines[i].contains("$priv_tracking")) {
+			bool tracking = p_preset->get("privacy/tracking_enabled");
+			strnew += "\t<key>NSPrivacyTracking</key>\n";
+			if (tracking) {
+				strnew += "\t<true/>\n";
+			} else {
+				strnew += "\t<false/>\n";
+			}
+			Vector<String> tracking_domains = p_preset->get("privacy/tracking_domains");
+			if (!tracking_domains.is_empty()) {
+				strnew += "\t<key>NSPrivacyTrackingDomains</key>\n";
+				strnew += "\t<array>\n";
+				for (const String &E : tracking_domains) {
+					strnew += "\t\t<string>" + E + "</string>\n";
+				}
+				strnew += "\t</array>\n";
+			}
+		} else if (lines[i].contains("$priv_api_types")) {
+			strnew += "\t<array>\n";
+			for (uint64_t j = 0; j < std::size(api_info); ++j) {
+				int api_access = p_preset->get(vformat("privacy/%s_access_reasons", api_info[j].prop_name));
+				if (api_access != 0) {
+					strnew += "\t\t<dict>\n";
+					strnew += "\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n";
+					strnew += "\t\t\t<array>\n";
+					for (int k = 0; k < api_info[j].prop_flag_value.size(); k++) {
+						if (api_access & (1 << k)) {
+							strnew += vformat("\t\t\t\t<string>%s</string>\n", api_info[j].prop_flag_value[k]);
+						}
+					}
+					strnew += "\t\t\t</array>\n";
+					strnew += "\t\t\t<key>NSPrivacyAccessedAPIType</key>\n";
+					strnew += vformat("\t\t\t<string>%s</string>\n", api_info[j].type_name);
+					strnew += "\t\t</dict>\n";
+				}
+			}
+			strnew += "\t</array>\n";
+		} else {
+			strnew += lines[i] + "\n";
+		}
+	}
+
+	// !BAS! I'm assuming the 9 in the original code was a typo. I've added -1 or else it seems to also be adding our terminating zero...
+	// should apply the same fix in our macOS export.
+	CharString cs = strnew.utf8();
+	pfile.resize(cs.size() - 1);
+	for (int i = 0; i < cs.size() - 1; i++) {
+		pfile.write[i] = cs[i];
+	}
+}
+
+String EditorExportPlatformAppleEmbedded::_get_additional_plist_content() {
+	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		result += export_plugins[i]->get_apple_embedded_platform_plist_content();
+	}
+	return result;
+}
+
+String EditorExportPlatformAppleEmbedded::_get_linker_flags() {
+	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		String flags = export_plugins[i]->get_apple_embedded_platform_linker_flags();
+		if (flags.length() == 0) {
+			continue;
+		}
+		if (result.length() > 0) {
+			result += ' ';
+		}
+		result += flags;
+	}
+	// the flags will be enclosed in quotes, so need to escape them
+	return result.replace("\"", "\\\"");
+}
+
+String EditorExportPlatformAppleEmbedded::_get_cpp_code() {
+	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	String result;
+	for (int i = 0; i < export_plugins.size(); ++i) {
+		result += export_plugins[i]->get_apple_embedded_platform_cpp_code();
+	}
+	return result;
+}
+
+void EditorExportPlatformAppleEmbedded::_blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot) {
+	ERR_FAIL_COND(p_dst.is_null());
+	ERR_FAIL_COND(p_src.is_null());
+
+	int sw = p_rot ? p_src->get_height() : p_src->get_width();
+	int sh = p_rot ? p_src->get_width() : p_src->get_height();
+
+	int x_pos = (p_dst->get_width() - sw) / 2;
+	int y_pos = (p_dst->get_height() - sh) / 2;
+
+	int xs = (x_pos >= 0) ? 0 : -x_pos;
+	int ys = (y_pos >= 0) ? 0 : -y_pos;
+
+	if (sw + x_pos > p_dst->get_width()) {
+		sw = p_dst->get_width() - x_pos;
+	}
+	if (sh + y_pos > p_dst->get_height()) {
+		sh = p_dst->get_height() - y_pos;
+	}
+
+	for (int y = ys; y < sh; y++) {
+		for (int x = xs; x < sw; x++) {
+			Color sc = p_rot ? p_src->get_pixel(p_src->get_width() - y - 1, x) : p_src->get_pixel(x, y);
+			Color dc = p_dst->get_pixel(x_pos + x, y_pos + y);
+			dc.r = (double)(sc.a * sc.r + dc.a * (1.0 - sc.a) * dc.r);
+			dc.g = (double)(sc.a * sc.g + dc.a * (1.0 - sc.a) * dc.g);
+			dc.b = (double)(sc.a * sc.b + dc.a * (1.0 - sc.a) * dc.b);
+			dc.a = (double)(sc.a + dc.a * (1.0 - sc.a));
+			p_dst->set_pixel(x_pos + x, y_pos + y, dc);
+		}
+	}
+}
+
+Error EditorExportPlatformAppleEmbedded::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata) {
+	Vector<String> dirs;
+	String current_dir = p_da->get_current_dir();
+	p_da->list_dir_begin();
+	String path = p_da->get_next();
+	while (!path.is_empty()) {
+		if (p_da->current_is_dir()) {
+			if (path != "." && path != "..") {
+				dirs.push_back(path);
+			}
+		} else {
+			Error err = p_handler(current_dir.path_join(path), p_userdata);
+			if (err) {
+				p_da->list_dir_end();
+				return err;
+			}
+		}
+		path = p_da->get_next();
+	}
+	p_da->list_dir_end();
+
+	for (int i = 0; i < dirs.size(); ++i) {
+		p_da->change_dir(dirs[i]);
+		Error err = _walk_dir_recursive(p_da, p_handler, p_userdata);
+		p_da->change_dir("..");
+		if (err) {
+			return err;
+		}
+	}
+
+	return OK;
+}
+
+struct CodesignData {
+	const Ref<EditorExportPreset> &preset;
+	bool debug = false;
+
+	CodesignData(const Ref<EditorExportPreset> &p_preset, bool p_debug) :
+			preset(p_preset),
+			debug(p_debug) {
+	}
+};
+
+Error EditorExportPlatformAppleEmbedded::_codesign(String p_file, void *p_userdata) {
+	if (p_file.ends_with(".dylib")) {
+		CodesignData *data = static_cast<CodesignData *>(p_userdata);
+		print_line(String("Signing ") + p_file);
+
+		String sign_id;
+		if (data->debug) {
+			sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "Apple Development" : data->preset->get("application/code_sign_identity_debug");
+		} else {
+			sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "Apple Distribution" : data->preset->get("application/code_sign_identity_release");
+		}
+
+		List<String> codesign_args;
+		codesign_args.push_back("-f");
+		codesign_args.push_back("-s");
+		codesign_args.push_back(sign_id);
+		codesign_args.push_back(p_file);
+		String str;
+		Error err = OS::get_singleton()->execute("codesign", codesign_args, &str, nullptr, true);
+		print_verbose("codesign (" + p_file + "):\n" + str);
+
+		return err;
+	}
+	return OK;
+}
+
+struct PbxId {
+private:
+	static char _hex_char(uint8_t p_four_bits) {
+		if (p_four_bits < 10) {
+			return ('0' + p_four_bits);
+		}
+		return 'A' + (p_four_bits - 10);
+	}
+
+	static String _hex_pad(uint32_t p_num) {
+		Vector<char> ret;
+		ret.resize(sizeof(p_num) * 2);
+		for (uint64_t i = 0; i < sizeof(p_num) * 2; ++i) {
+			uint8_t four_bits = (p_num >> (sizeof(p_num) * 8 - (i + 1) * 4)) & 0xF;
+			ret.write[i] = _hex_char(four_bits);
+		}
+		return String::utf8(ret.ptr(), ret.size());
+	}
+
+public:
+	uint32_t high_bits;
+	uint32_t mid_bits;
+	uint32_t low_bits;
+
+	String str() const {
+		return _hex_pad(high_bits) + _hex_pad(mid_bits) + _hex_pad(low_bits);
+	}
+
+	PbxId &operator++() {
+		low_bits++;
+		if (!low_bits) {
+			mid_bits++;
+			if (!mid_bits) {
+				high_bits++;
+			}
+		}
+
+		return *this;
+	}
+};
+
+struct ExportLibsData {
+	Vector<String> lib_paths;
+	String dest_dir;
+};
+
+void EditorExportPlatformAppleEmbedded::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const {
+	Ref<PList> plist;
+	plist.instantiate();
+	plist->load_file(p_path.path_join("Info.plist"));
+	Ref<PListNode> root_node = plist->get_root();
+	if (root_node.is_null()) {
+		return;
+	}
+	Dictionary root = root_node->get_value();
+	if (!root.has("AvailableLibraries")) {
+		return;
+	}
+	Ref<PListNode> libs_node = root["AvailableLibraries"];
+	if (libs_node.is_null()) {
+		return;
+	}
+	Array libs = libs_node->get_value();
+	r_total_libs = libs.size();
+	for (int j = 0; j < libs.size(); j++) {
+		Ref<PListNode> lib_node = libs[j];
+		if (lib_node.is_null()) {
+			return;
+		}
+		Dictionary lib = lib_node->get_value();
+		if (lib.has("BinaryPath")) {
+			Ref<PListNode> path_node = lib["BinaryPath"];
+			if (path_node.is_valid()) {
+				String path = path_node->get_value();
+				if (path.ends_with(".a")) {
+					r_static_libs++;
+				}
+				if (path.ends_with(".dylib")) {
+					r_dylibs++;
+				}
+				if (path.ends_with(".framework")) {
+					r_frameworks++;
+				}
+			}
+		}
+	}
+}
+
+Error EditorExportPlatformAppleEmbedded::_convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const {
+	print_line("Converting to .framework", p_source, " -> ", p_destination);
+
+	Ref<DirAccess> da = DirAccess::create_for_path(p_source);
+	if (da.is_null()) {
+		return ERR_CANT_OPEN;
+	}
+
+	Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	if (filesystem_da.is_null()) {
+		return ERR_CANT_OPEN;
+	}
+
+	if (!filesystem_da->dir_exists(p_destination)) {
+		Error make_dir_err = filesystem_da->make_dir_recursive(p_destination);
+		if (make_dir_err) {
+			return make_dir_err;
+		}
+	}
+
+	String asset = p_source.ends_with("/") ? p_source.left(-1) : p_source;
+	if (asset.ends_with(".xcframework")) {
+		Ref<PList> plist;
+		plist.instantiate();
+		plist->load_file(p_source.path_join("Info.plist"));
+		Ref<PListNode> root_node = plist->get_root();
+		if (root_node.is_null()) {
+			return ERR_CANT_OPEN;
+		}
+		Dictionary root = root_node->get_value();
+		if (!root.has("AvailableLibraries")) {
+			return ERR_CANT_OPEN;
+		}
+		Ref<PListNode> libs_node = root["AvailableLibraries"];
+		if (libs_node.is_null()) {
+			return ERR_CANT_OPEN;
+		}
+		Array libs = libs_node->get_value();
+		for (int j = 0; j < libs.size(); j++) {
+			Ref<PListNode> lib_node = libs[j];
+			if (lib_node.is_null()) {
+				return ERR_CANT_OPEN;
+			}
+			Dictionary lib = lib_node->get_value();
+			if (lib.has("BinaryPath") && lib.has("LibraryPath") && lib.has("LibraryIdentifier")) {
+				Ref<PListNode> bpath_node = lib["BinaryPath"];
+				Ref<PListNode> lpath_node = lib["LibraryPath"];
+				Ref<PListNode> lid_node = lib["LibraryIdentifier"];
+				if (bpath_node.is_valid() && lpath_node.is_valid() && lid_node.is_valid()) {
+					String binary_path = bpath_node->get_value();
+					String library_identifier = lid_node->get_value();
+
+					String file_name = binary_path.get_basename().get_file();
+					String framework_name = file_name + ".framework";
+
+					bpath_node->data_string = framework_name.utf8();
+					lpath_node->data_string = framework_name.utf8();
+					if (!filesystem_da->dir_exists(p_destination.path_join(library_identifier))) {
+						filesystem_da->make_dir_recursive(p_destination.path_join(library_identifier));
+					}
+					_convert_to_framework(p_source.path_join(library_identifier).path_join(binary_path), p_destination.path_join(library_identifier).path_join(framework_name), p_id);
+					if (lib.has("DebugSymbolsPath")) {
+						Ref<PListNode> dpath_node = lib["DebugSymbolsPath"];
+						if (dpath_node.is_valid()) {
+							String dpath = dpath_node->get_value();
+							if (da->dir_exists(p_source.path_join(library_identifier).path_join(dpath))) {
+								da->copy_dir(p_source.path_join(library_identifier).path_join(dpath), p_destination.path_join(library_identifier).path_join("dSYMs"));
+							}
+						}
+					}
+				}
+			}
+		}
+		String info_plist = plist->save_text();
+
+		Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE);
+		if (f.is_valid()) {
+			f->store_string(info_plist);
+		}
+	} else {
+		String file_name = p_destination.get_basename().get_file();
+		String framework_name = file_name + ".framework";
+
+		da->copy(p_source, p_destination.path_join(file_name));
+
+		// Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib
+		{
+			List<String> install_name_args;
+			install_name_args.push_back("-id");
+			install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name));
+			install_name_args.push_back(p_destination.path_join(file_name));
+
+			OS::get_singleton()->execute("install_name_tool", install_name_args);
+		}
+
+		// Creating Info.plist
+		{
+			String lib_clean_name = file_name;
+			for (int i = 0; i < lib_clean_name.length(); i++) {
+				if (!is_ascii_alphanumeric_char(lib_clean_name[i]) && lib_clean_name[i] != '.' && lib_clean_name[i] != '-') {
+					lib_clean_name[i] = '-';
+				}
+			}
+			String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+									   "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+									   "<plist version=\"1.0\">\n"
+									   "  <dict>\n"
+									   "    <key>CFBundleShortVersionString</key>\n"
+									   "    <string>1.0</string>\n"
+									   "    <key>CFBundleIdentifier</key>\n"
+									   "    <string>$id.framework.$cl_name</string>\n"
+									   "    <key>CFBundleName</key>\n"
+									   "    <string>$name</string>\n"
+									   "    <key>CFBundleExecutable</key>\n"
+									   "    <string>$name</string>\n"
+									   "    <key>DTPlatformName</key>\n"
+									   "    <string>iphoneos</string>\n"
+									   "    <key>CFBundleInfoDictionaryVersion</key>\n"
+									   "    <string>6.0</string>\n"
+									   "    <key>CFBundleVersion</key>\n"
+									   "    <string>1</string>\n"
+									   "    <key>CFBundlePackageType</key>\n"
+									   "    <string>FMWK</string>\n"
+									   "    <key>MinimumOSVersion</key>\n"
+									   "    <string>12.0</string>\n"
+									   "  </dict>\n"
+									   "</plist>";
+
+			String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name).replace("$cl_name", lib_clean_name);
+
+			Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE);
+			if (f.is_valid()) {
+				f->store_string(info_plist);
+			}
+		}
+	}
+
+	return OK;
+}
+
+void EditorExportPlatformAppleEmbedded::_add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<AppleEmbeddedExportAsset> &p_additional_assets) {
+	// that is just a random number, we just need Godot IDs not to clash with
+	// existing IDs in the project.
+	PbxId current_id = { 0x58938401, 0, 0 };
+	String pbx_files;
+	String pbx_frameworks_build;
+	String pbx_frameworks_refs;
+	String pbx_resources_build;
+	String pbx_resources_refs;
+	String pbx_embeded_frameworks;
+
+	const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") +
+			"$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n";
+
+	for (int i = 0; i < p_additional_assets.size(); ++i) {
+		String additional_asset_info_format = file_info_format;
+
+		String build_id = (++current_id).str();
+		String ref_id = (++current_id).str();
+		String framework_id = "";
+
+		const AppleEmbeddedExportAsset &asset = p_additional_assets[i];
+
+		String type;
+		if (asset.exported_path.ends_with(".framework")) {
+			if (asset.should_embed) {
+				additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+				framework_id = (++current_id).str();
+				pbx_embeded_frameworks += framework_id + ",\n";
+			}
+
+			type = "wrapper.framework";
+		} else if (asset.exported_path.ends_with(".xcframework")) {
+			int total_libs = 0;
+			int static_libs = 0;
+			int dylibs = 0;
+			int frameworks = 0;
+			_check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks);
+			if (asset.should_embed && static_libs != total_libs) {
+				additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n";
+				framework_id = (++current_id).str();
+				pbx_embeded_frameworks += framework_id + ",\n";
+			}
+
+			type = "wrapper.xcframework";
+		} else if (asset.exported_path.ends_with(".dylib")) {
+			type = "compiled.mach-o.dylib";
+		} else if (asset.exported_path.ends_with(".a")) {
+			type = "archive.ar";
+		} else {
+			type = "file";
+		}
+
+		String &pbx_build = asset.is_framework ? pbx_frameworks_build : pbx_resources_build;
+		String &pbx_refs = asset.is_framework ? pbx_frameworks_refs : pbx_resources_refs;
+
+		if (pbx_build.length() > 0) {
+			pbx_build += ",\n";
+			pbx_refs += ",\n";
+		}
+		pbx_build += build_id;
+		pbx_refs += ref_id;
+
+		Dictionary format_dict;
+		format_dict["build_id"] = build_id;
+		format_dict["ref_id"] = ref_id;
+		format_dict["name"] = asset.exported_path.get_file();
+		format_dict["file_path"] = asset.exported_path;
+		format_dict["file_type"] = type;
+		if (framework_id.length() > 0) {
+			format_dict["framework_id"] = framework_id;
+		}
+		pbx_files += additional_asset_info_format.format(format_dict, "$_");
+	}
+
+	// Note, frameworks like gamekit are always included in our project.pbxprof file
+	// even if turned off in capabilities.
+
+	String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
+	str = str.replace("$additional_pbx_files", pbx_files);
+	str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build);
+	str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs);
+	str = str.replace("$additional_pbx_resources_build", pbx_resources_build);
+	str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs);
+	str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks);
+
+	CharString cs = str.utf8();
+	p_project_data.resize(cs.size() - 1);
+	for (int i = 0; i < cs.size() - 1; i++) {
+		p_project_data.write[i] = cs[i];
+	}
+}
+
+Error EditorExportPlatformAppleEmbedded::_copy_asset(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<AppleEmbeddedExportAsset> &r_exported_assets) {
+	String binary_name = p_out_dir.get_file().get_basename();
+
+	Ref<DirAccess> da = DirAccess::create_for_path(p_asset);
+	if (da.is_null()) {
+		ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open directory: " + p_asset + ".");
+	}
+	bool file_exists = da->file_exists(p_asset);
+	bool dir_exists = da->dir_exists(p_asset);
+	if (!file_exists && !dir_exists) {
+		return ERR_FILE_NOT_FOUND;
+	}
+
+	String base_dir = p_asset.get_base_dir().replace("res://", "").replace(".godot/mono/temp/bin/", "");
+	String asset = p_asset.ends_with("/") ? p_asset.left(-1) : p_asset;
+	String destination_dir;
+	String destination;
+	String asset_path;
+
+	Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+
+	if (p_is_framework && asset.ends_with(".dylib")) {
+		// For Apple Embedded platforms we need to turn .dylib into .framework
+		// to be able to send application to AppStore
+		asset_path = String("dylibs").path_join(base_dir);
+
+		String file_name;
+
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_basename().get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
+
+		String framework_name = file_name + ".framework";
+
+		asset_path = asset_path.path_join(framework_name);
+		destination_dir = p_out_dir.path_join(asset_path);
+		destination = destination_dir;
+
+		// Convert to framework and copy.
+		Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier"));
+		if (err) {
+			return err;
+		}
+	} else if (p_is_framework && asset.ends_with(".xcframework")) {
+		// For Apple Embedded platforms we need to turn .dylib inside .xcframework
+		// into .framework to be able to send application to AppStore
+
+		int total_libs = 0;
+		int static_libs = 0;
+		int dylibs = 0;
+		int frameworks = 0;
+		_check_xcframework_content(p_asset, total_libs, static_libs, dylibs, frameworks);
+
+		asset_path = String("dylibs").path_join(base_dir);
+		String file_name;
+
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
+
+		asset_path = asset_path.path_join(file_name);
+		destination_dir = p_out_dir.path_join(asset_path);
+		destination = destination_dir;
+
+		if (dylibs > 0) {
+			// Convert to framework and copy.
+			Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier"));
+			if (err) {
+				return err;
+			}
+		} else {
+			// Copy as is.
+			if (!filesystem_da->dir_exists(destination_dir)) {
+				Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+				if (make_dir_err) {
+					return make_dir_err;
+				}
+			}
+			Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+			if (err) {
+				return err;
+			}
+		}
+	} else if (p_is_framework && asset.ends_with(".framework")) {
+		// Framework.
+		asset_path = String("dylibs").path_join(base_dir);
+
+		String file_name;
+
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
+
+		asset_path = asset_path.path_join(file_name);
+		destination_dir = p_out_dir.path_join(asset_path);
+		destination = destination_dir;
+
+		// Copy as is.
+		if (!filesystem_da->dir_exists(destination_dir)) {
+			Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+			if (make_dir_err) {
+				return make_dir_err;
+			}
+		}
+		Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+		if (err) {
+			return err;
+		}
+	} else {
+		// Unknown resource.
+		asset_path = base_dir;
+
+		String file_name;
+
+		if (!p_custom_file_name) {
+			file_name = p_asset.get_file();
+		} else {
+			file_name = *p_custom_file_name;
+		}
+
+		destination_dir = p_out_dir.path_join(asset_path);
+		asset_path = asset_path.path_join(file_name);
+		destination = p_out_dir.path_join(asset_path);
+
+		// Copy as is.
+		if (!filesystem_da->dir_exists(destination_dir)) {
+			Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
+			if (make_dir_err) {
+				return make_dir_err;
+			}
+		}
+		Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination);
+		if (err) {
+			return err;
+		}
+	}
+
+	if (asset_path.ends_with("/")) {
+		asset_path = asset_path.left(-1);
+	}
+	AppleEmbeddedExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed };
+	r_exported_assets.push_back(exported_asset);
+
+	return OK;
+}
+
+Error EditorExportPlatformAppleEmbedded::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<AppleEmbeddedExportAsset> &r_exported_assets) {
+	for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) {
+		const String &asset = p_assets[f_idx];
+		if (asset.begins_with("res://")) {
+			Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets);
+			ERR_FAIL_COND_V(err != OK, err);
+		} else if (asset.is_absolute_path() && ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) {
+			Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets);
+			ERR_FAIL_COND_V(err != OK, err);
+		} else {
+			// either SDK-builtin or already a part of the export template
+			AppleEmbeddedExportAsset exported_asset = { asset, p_is_framework, p_should_embed };
+			r_exported_assets.push_back(exported_asset);
+		}
+	}
+
+	return OK;
+}
+
+Error EditorExportPlatformAppleEmbedded::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<AppleEmbeddedExportAsset> &r_exported_assets) {
+	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	for (int i = 0; i < export_plugins.size(); i++) {
+		Vector<String> linked_frameworks = export_plugins[i]->get_apple_embedded_platform_frameworks();
+		Error err = _export_additional_assets(p_preset, p_out_dir, linked_frameworks, true, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+
+		Vector<String> embedded_frameworks = export_plugins[i]->get_apple_embedded_platform_embedded_frameworks();
+		err = _export_additional_assets(p_preset, p_out_dir, embedded_frameworks, true, true, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+
+		Vector<String> project_static_libs = export_plugins[i]->get_apple_embedded_platform_project_static_libs();
+		for (int j = 0; j < project_static_libs.size(); j++) {
+			project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
+		}
+		err = _export_additional_assets(p_preset, p_out_dir, project_static_libs, true, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+
+		Vector<String> apple_embedded_platform_bundle_files = export_plugins[i]->get_apple_embedded_platform_bundle_files();
+		err = _export_additional_assets(p_preset, p_out_dir, apple_embedded_platform_bundle_files, false, false, r_exported_assets);
+		ERR_FAIL_COND_V(err, err);
+	}
+
+	Vector<String> library_paths;
+	for (int i = 0; i < p_libraries.size(); ++i) {
+		library_paths.push_back(p_libraries[i].path);
+	}
+	Error err = _export_additional_assets(p_preset, p_out_dir, library_paths, true, true, r_exported_assets);
+	ERR_FAIL_COND_V(err, err);
+
+	return OK;
+}
+
+Vector<String> EditorExportPlatformAppleEmbedded::_get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const {
+	Vector<ExportArchitecture> all_archs = _get_supported_architectures();
+	Vector<String> enabled_archs;
+	for (int i = 0; i < all_archs.size(); ++i) {
+		bool is_enabled = p_preset->get("architectures/" + all_archs[i].name);
+		if (is_enabled) {
+			enabled_archs.push_back(all_archs[i].name);
+		}
+	}
+	return enabled_archs;
+}
+
+Error EditorExportPlatformAppleEmbedded::_export_apple_embedded_plugins(const Ref<EditorExportPreset> &p_preset, AppleEmbeddedConfigData &p_config_data, const String &dest_dir, Vector<AppleEmbeddedExportAsset> &r_exported_assets, bool p_debug) {
+	String plugin_definition_cpp_code;
+	String plugin_initialization_cpp_code;
+	String plugin_deinitialization_cpp_code;
+
+	Vector<String> plugin_linked_dependencies;
+	Vector<String> plugin_embedded_dependencies;
+	Vector<String> plugin_files;
+
+	Vector<PluginConfigAppleEmbedded> enabled_plugins = get_enabled_plugins(get_platform_name(), p_preset);
+
+	Vector<String> added_linked_dependenciy_names;
+	Vector<String> added_embedded_dependenciy_names;
+	HashMap<String, String> plist_values;
+
+	HashSet<String> plugin_linker_flags;
+
+	Error err;
+
+	for (int i = 0; i < enabled_plugins.size(); i++) {
+		PluginConfigAppleEmbedded plugin = enabled_plugins[i];
+
+		// Export plugin binary.
+		String plugin_main_binary = PluginConfigAppleEmbedded::get_plugin_main_binary(plugin, p_debug);
+		String plugin_binary_result_file = plugin.binary.get_file();
+		// We shouldn't embed .xcframework that contains static libraries.
+		// Static libraries are not embedded anyway.
+		err = _copy_asset(p_preset, dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets);
+		ERR_FAIL_COND_V(err != OK, err);
+
+		// Adding dependencies.
+		// Use separate container for names to check for duplicates.
+		for (int j = 0; j < plugin.linked_dependencies.size(); j++) {
+			String dependency = plugin.linked_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_linked_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_linked_dependenciy_names.push_back(name);
+			plugin_linked_dependencies.push_back(dependency);
+		}
+
+		for (int j = 0; j < plugin.system_dependencies.size(); j++) {
+			String dependency = plugin.system_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_linked_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_linked_dependenciy_names.push_back(name);
+			plugin_linked_dependencies.push_back(dependency);
+		}
+
+		for (int j = 0; j < plugin.embedded_dependencies.size(); j++) {
+			String dependency = plugin.embedded_dependencies[j];
+			String name = dependency.get_file();
+
+			if (added_embedded_dependenciy_names.has(name)) {
+				continue;
+			}
+
+			added_embedded_dependenciy_names.push_back(name);
+			plugin_embedded_dependencies.push_back(dependency);
+		}
+
+		plugin_files.append_array(plugin.files_to_copy);
+
+		// Capabilities
+		// Also checking for duplicates.
+		for (int j = 0; j < plugin.capabilities.size(); j++) {
+			String capability = plugin.capabilities[j];
+
+			if (p_config_data.capabilities.has(capability)) {
+				continue;
+			}
+
+			p_config_data.capabilities.push_back(capability);
+		}
+
+		// Linker flags
+		// Checking duplicates
+		for (int j = 0; j < plugin.linker_flags.size(); j++) {
+			String linker_flag = plugin.linker_flags[j];
+			plugin_linker_flags.insert(linker_flag);
+		}
+
+		// Plist
+		// Using hash map container to remove duplicates
+
+		for (const KeyValue<String, PluginConfigAppleEmbedded::PlistItem> &E : plugin.plist) {
+			String key = E.key;
+			const PluginConfigAppleEmbedded::PlistItem &item = E.value;
+
+			String value;
+
+			switch (item.type) {
+				case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: {
+					String preset_name = "plugins_plist/" + key;
+					String input_value = p_preset->get(preset_name);
+					value = "<string>" + input_value + "</string>";
+				} break;
+				default:
+					value = item.value;
+					break;
+			}
+
+			if (key.is_empty() || value.is_empty()) {
+				continue;
+			}
+
+			String plist_key = "<key>" + key + "</key>";
+
+			plist_values[plist_key] = value;
+		}
+
+		// CPP Code
+		String definition_comment = "// Plugin: " + plugin.name + "\n";
+		String initialization_method = plugin.initialization_method + "();\n";
+		String deinitialization_method = plugin.deinitialization_method + "();\n";
+
+		plugin_definition_cpp_code += definition_comment +
+				"extern void " + initialization_method +
+				"extern void " + deinitialization_method + "\n";
+
+		plugin_initialization_cpp_code += "\t" + initialization_method;
+		plugin_deinitialization_cpp_code += "\t" + deinitialization_method;
+
+		if (plugin.use_swift_runtime) {
+			p_config_data.use_swift_runtime = true;
+		}
+	}
+
+	// Updating `Info.plist`
+	{
+		for (const KeyValue<String, String> &E : plist_values) {
+			String key = E.key;
+			String value = E.value;
+
+			if (key.is_empty() || value.is_empty()) {
+				continue;
+			}
+
+			p_config_data.plist_content += key + value + "\n";
+		}
+	}
+
+	// Export files
+	{
+		// Export linked plugin dependency
+		err = _export_additional_assets(p_preset, dest_dir, plugin_linked_dependencies, true, false, r_exported_assets);
+		ERR_FAIL_COND_V(err != OK, err);
+
+		// Export embedded plugin dependency
+		err = _export_additional_assets(p_preset, dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets);
+		ERR_FAIL_COND_V(err != OK, err);
+
+		// Export plugin files
+		err = _export_additional_assets(p_preset, dest_dir, plugin_files, false, false, r_exported_assets);
+		ERR_FAIL_COND_V(err != OK, err);
+	}
+
+	// Update CPP
+	{
+		Dictionary plugin_format;
+		plugin_format["definition"] = plugin_definition_cpp_code;
+		plugin_format["initialization"] = plugin_initialization_cpp_code;
+		plugin_format["deinitialization"] = plugin_deinitialization_cpp_code;
+
+		String plugin_cpp_code = "\n// Godot Plugins\n"
+								 "void godot_apple_embedded_plugins_initialize();\n"
+								 "void godot_apple_embedded_plugins_deinitialize();\n"
+								 "// Exported Plugins\n\n"
+								 "$definition"
+								 "// Use Plugins\n"
+								 "void godot_apple_embedded_plugins_initialize() {\n"
+								 "$initialization"
+								 "}\n\n"
+								 "void godot_apple_embedded_plugins_deinitialize() {\n"
+								 "$deinitialization"
+								 "}\n";
+
+		p_config_data.cpp_code += plugin_cpp_code.format(plugin_format, "$_");
+	}
+
+	// Update Linker Flag Values
+	{
+		String result_linker_flags = " ";
+		for (const String &E : plugin_linker_flags) {
+			const String &flag = E;
+
+			if (flag.length() == 0) {
+				continue;
+			}
+
+			if (result_linker_flags.length() > 0) {
+				result_linker_flags += ' ';
+			}
+
+			result_linker_flags += flag;
+		}
+		result_linker_flags = result_linker_flags.replace("\"", "\\\"");
+		p_config_data.linker_flags += result_linker_flags;
+	}
+
+	return OK;
+}
+
+Error EditorExportPlatformAppleEmbedded::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+	return _export_project_helper(p_preset, p_debug, p_path, p_flags, false);
+}
+
+Error EditorExportPlatformAppleEmbedded::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_oneclick) {
+	ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+	const String dest_dir = p_path.get_base_dir() + "/";
+	const String binary_name = p_path.get_file().get_basename();
+	const String binary_dir = dest_dir + binary_name;
+
+	if (!DirAccess::exists(dest_dir)) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Target folder does not exist or is inaccessible: \"%s\""), dest_dir));
+		return ERR_FILE_BAD_PATH;
+	}
+
+	bool export_project_only = p_preset->get("application/export_project_only");
+	if (p_oneclick) {
+		export_project_only = false; // Skip for one-click deploy.
+	}
+
+	EditorProgress ep("export", export_project_only ? TTR("Exporting for " + get_name() + " (Project Files Only)") : TTR("Exporting for " + get_name() + ""), export_project_only ? 2 : 5, true);
+
+	String team_id = p_preset->get("application/app_store_team_id");
+	ERR_FAIL_COND_V_MSG(team_id.length() == 0, ERR_CANT_OPEN, "App Store Team ID not specified - cannot configure the project.");
+
+	String src_pkg_name;
+	if (p_debug) {
+		src_pkg_name = p_preset->get("custom_template/debug");
+	} else {
+		src_pkg_name = p_preset->get("custom_template/release");
+	}
+
+	if (src_pkg_name.is_empty()) {
+		String err;
+		src_pkg_name = find_export_template(get_platform_name() + ".zip", &err);
+		if (src_pkg_name.is_empty()) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found."));
+			return ERR_FILE_NOT_FOUND;
+		}
+	}
+
+	{
+		bool delete_old = p_preset->get("application/delete_old_export_files_unconditionally");
+		Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+		if (da.is_valid()) {
+			String current_dir = da->get_current_dir();
+
+			// Remove leftovers from last export so they don't interfere in case some files are no longer needed.
+			if (da->change_dir(binary_dir + ".xcodeproj") == OK) {
+				// Check directory content before deleting.
+				int expected_files = 0;
+				int total_files = 0;
+				if (!delete_old) {
+					da->list_dir_begin();
+					for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) {
+						if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files.
+							if (da->current_is_dir()) {
+								if (n == "xcshareddata" || n == "project.xcworkspace") {
+									expected_files++;
+								}
+							} else {
+								if (n == "project.pbxproj") {
+									expected_files++;
+								}
+							}
+							total_files++;
+						}
+					}
+					da->list_dir_end();
+				}
+				if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) {
+					da->erase_contents_recursive();
+				} else {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s.xcodeproj\", delete it manually or select another destination."), binary_dir));
+					return ERR_CANT_CREATE;
+				}
+			}
+			da->change_dir(current_dir);
+
+			if (da->change_dir(binary_dir) == OK) {
+				// Check directory content before deleting.
+				int expected_files = 0;
+				int total_files = 0;
+				if (!delete_old) {
+					da->list_dir_begin();
+					for (String n = da->get_next(); !n.is_empty(); n = da->get_next()) {
+						if (!n.begins_with(".")) { // Ignore ".", ".." and hidden files.
+							if (da->current_is_dir()) {
+								if (n == "dylibs" || n == "Images.xcassets" || n.ends_with(".lproj") || n == "godot-publish-dotnet" || n.ends_with(".xcframework") || n.ends_with(".framework")) {
+									expected_files++;
+								}
+							} else {
+								if (n == binary_name + "-Info.plist" || n == binary_name + ".entitlements" || n == "Launch Screen.storyboard" || n == "export_options.plist" || n.begins_with("dummy.") || n.ends_with(".gdip")) {
+									expected_files++;
+								}
+							}
+							total_files++;
+						}
+					}
+					da->list_dir_end();
+				}
+				if ((total_files == 0) || (expected_files >= Math::floor(total_files * 0.8))) {
+					da->erase_contents_recursive();
+				} else {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Unexpected files found in the export destination directory \"%s\", delete it manually or select another destination."), binary_dir));
+					return ERR_CANT_CREATE;
+				}
+			}
+			da->change_dir(current_dir);
+
+			if (!da->dir_exists(binary_dir)) {
+				Error err = da->make_dir(binary_dir);
+				if (err != OK) {
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create the directory: \"%s\""), binary_dir));
+					return err;
+				}
+			}
+		}
+	}
+
+	if (ep.step("Making .pck", 0)) {
+		return ERR_SKIP;
+	}
+	String pack_path = binary_dir + ".pck";
+	Vector<SharedObject> libraries;
+	Error err = save_pack(p_preset, p_debug, pack_path, &libraries);
+	if (err) {
+		// Message is supplied by the subroutine method.
+		return err;
+	}
+
+	if (ep.step("Extracting and configuring Xcode project", 1)) {
+		return ERR_SKIP;
+	}
+
+	String library_to_use = "libgodot." + get_platform_name() + "." + String(p_debug ? "debug" : "release") + ".xcframework";
+
+	print_line("Static framework: " + library_to_use);
+	String pkg_name;
+	if (String(get_project_setting(p_preset, "application/config/name")) != "") {
+		pkg_name = String(get_project_setting(p_preset, "application/config/name"));
+	} else {
+		pkg_name = "Unnamed";
+	}
+
+	bool found_library = false;
+
+	const String godot_platform = "godot_" + get_platform_name();
+	const String project_file = godot_platform + ".xcodeproj/project.pbxproj";
+	HashSet<String> files_to_parse;
+	files_to_parse.insert(godot_platform + "/godot_" + get_platform_name() + "-Info.plist");
+	files_to_parse.insert(project_file);
+	files_to_parse.insert(godot_platform + "/export_options.plist");
+	files_to_parse.insert(godot_platform + "/dummy.cpp");
+	files_to_parse.insert(godot_platform + ".xcodeproj/project.xcworkspace/contents.xcworkspacedata");
+	files_to_parse.insert(godot_platform + ".xcodeproj/xcshareddata/xcschemes/godot_" + get_platform_name() + ".xcscheme");
+	files_to_parse.insert(godot_platform + "/godot_" + get_platform_name() + ".entitlements");
+	files_to_parse.insert(godot_platform + "/Launch Screen.storyboard");
+	files_to_parse.insert("PrivacyInfo.xcprivacy");
+
+	AppleEmbeddedConfigData config_data = {
+		pkg_name,
+		binary_name,
+		_get_additional_plist_content(),
+		String(" ").join(_get_preset_architectures(p_preset)),
+		_get_linker_flags(),
+		_get_cpp_code(),
+		"",
+		"",
+		"",
+		"",
+		Vector<String>(),
+		false
+	};
+
+	config_data.plist_content += p_preset->get("application/additional_plist_content").operator String() + "\n";
+
+	Vector<AppleEmbeddedExportAsset> assets;
+
+	Ref<DirAccess> tmp_app_path = DirAccess::create_for_path(dest_dir);
+	if (tmp_app_path.is_null()) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not create and open the directory: \"%s\""), dest_dir));
+		return ERR_CANT_CREATE;
+	}
+
+	print_line("Unzipping...");
+	Ref<FileAccess> io_fa;
+	zlib_filefunc_def io = zipio_create_io(&io_fa);
+	unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
+	if (!src_pkg_zip) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name));
+		return ERR_CANT_OPEN;
+	}
+
+	err = _export_apple_embedded_plugins(p_preset, config_data, binary_dir, assets, p_debug);
+	if (err != OK) {
+		// TODO: Improve error reporting by using `add_message` throughout all methods called via `_export_apple_embedded_plugins`.
+		// For now a generic top level message would be fine, but we're ought to use proper reporting here instead of
+		// just fail macros and non-descriptive error return values.
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Apple Embedded Plugins"), vformat(TTR("Failed to export Apple Embedded plugins with code %d. Please check the output log."), err));
+		return err;
+	}
+
+	//export rest of the files
+	int ret = unzGoToFirstFile(src_pkg_zip);
+	Vector<uint8_t> project_file_data;
+	while (ret == UNZ_OK) {
+#if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
+		bool is_execute = false;
+#endif
+
+		//get filename
+		unz_file_info info;
+		char fname[16384];
+		ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
+		if (ret != UNZ_OK) {
+			break;
+		}
+
+		String file = String::utf8(fname);
+
+		print_line("READ: " + file);
+		Vector<uint8_t> data;
+		data.resize(info.uncompressed_size);
+
+		//read
+		unzOpenCurrentFile(src_pkg_zip);
+		unzReadCurrentFile(src_pkg_zip, data.ptrw(), data.size());
+		unzCloseCurrentFile(src_pkg_zip);
+
+		//write
+
+		if (files_to_parse.has(file)) {
+			_fix_config_file(p_preset, data, config_data, p_debug);
+		} else if (file.begins_with("libgodot." + get_platform_name())) {
+			if (!file.begins_with(library_to_use) || file.ends_with(String("/empty"))) {
+				ret = unzGoToNextFile(src_pkg_zip);
+				continue; //ignore!
+			}
+			found_library = true;
+#if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
+			is_execute = true;
+#endif
+			file = file.replace(library_to_use, binary_name + ".xcframework");
+		}
+
+		if (file == project_file) {
+			project_file_data = data;
+		}
+
+		///@TODO need to parse logo files
+
+		if (data.size() > 0) {
+			file = file.replace("godot_" + get_platform_name(), binary_name);
+
+			print_line("ADDING: " + file + " size: " + itos(data.size()));
+
+			/* write it into our folder structure */
+			file = dest_dir + file;
+
+			/* make sure this folder exists */
+			String dir_name = file.get_base_dir();
+			if (!tmp_app_path->dir_exists(dir_name)) {
+				print_line("Creating " + dir_name);
+				Error dir_err = tmp_app_path->make_dir_recursive(dir_name);
+				if (dir_err) {
+					unzClose(src_pkg_zip);
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), dir_name));
+					return ERR_CANT_CREATE;
+				}
+			}
+
+			/* write the file */
+			{
+				Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE);
+				if (f.is_null()) {
+					unzClose(src_pkg_zip);
+					add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), file));
+					return ERR_CANT_CREATE;
+				};
+				f->store_buffer(data.ptr(), data.size());
+			}
+
+#if defined(MACOS_ENABLED) || defined(LINUXBSD_ENABLED)
+			if (is_execute) {
+				// we need execute rights on this file
+				chmod(file.utf8().get_data(), 0755);
+			}
+#endif
+		}
+
+		ret = unzGoToNextFile(src_pkg_zip);
+	}
+
+	// We're done with our source zip.
+	unzClose(src_pkg_zip);
+
+	if (!found_library) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template library '%s' not found. It might be missing from your template archive."), library_to_use));
+		return ERR_FILE_NOT_FOUND;
+	}
+
+	Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
+	Dictionary camera_usage_descriptions = p_preset->get("privacy/camera_usage_description_localized");
+	Dictionary microphone_usage_descriptions = p_preset->get("privacy/microphone_usage_description_localized");
+	Dictionary photolibrary_usage_descriptions = p_preset->get("privacy/photolibrary_usage_description_localized");
+
+	Vector<String> translations = get_project_setting(p_preset, "internationalization/locale/translations");
+	if (translations.size() > 0) {
+		{
+			String fname = binary_dir + "/en.lproj";
+			tmp_app_path->make_dir_recursive(fname);
+			Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+			f->store_line("/* Localized versions of Info.plist keys */");
+			f->store_line("");
+			f->store_line("CFBundleDisplayName = \"" + get_project_setting(p_preset, "application/config/name").operator String() + "\";");
+			f->store_line("NSCameraUsageDescription = \"" + p_preset->get("privacy/camera_usage_description").operator String() + "\";");
+			f->store_line("NSMicrophoneUsageDescription = \"" + p_preset->get("privacy/microphone_usage_description").operator String() + "\";");
+			f->store_line("NSPhotoLibraryUsageDescription = \"" + p_preset->get("privacy/photolibrary_usage_description").operator String() + "\";");
+		}
+
+		HashSet<String> languages;
+		for (const String &E : translations) {
+			Ref<Translation> tr = ResourceLoader::load(E);
+			if (tr.is_valid() && tr->get_locale() != "en") {
+				languages.insert(tr->get_locale());
+			}
+		}
+
+		for (const String &lang : languages) {
+			String fname = binary_dir + "/" + lang + ".lproj";
+			tmp_app_path->make_dir_recursive(fname);
+			Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+			f->store_line("/* Localized versions of Info.plist keys */");
+			f->store_line("");
+			if (appnames.has(lang)) {
+				f->store_line("CFBundleDisplayName = \"" + appnames[lang].operator String() + "\";");
+			}
+			if (camera_usage_descriptions.has(lang)) {
+				f->store_line("NSCameraUsageDescription = \"" + camera_usage_descriptions[lang].operator String() + "\";");
+			}
+			if (microphone_usage_descriptions.has(lang)) {
+				f->store_line("NSMicrophoneUsageDescription = \"" + microphone_usage_descriptions[lang].operator String() + "\";");
+			}
+			if (photolibrary_usage_descriptions.has(lang)) {
+				f->store_line("NSPhotoLibraryUsageDescription = \"" + photolibrary_usage_descriptions[lang].operator String() + "\";");
+			}
+		}
+	}
+
+	// Copy project static libs to the project
+	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+	for (int i = 0; i < export_plugins.size(); i++) {
+		Vector<String> project_static_libs = export_plugins[i]->get_apple_embedded_platform_project_static_libs();
+		for (int j = 0; j < project_static_libs.size(); j++) {
+			const String &static_lib_path = project_static_libs[j];
+			String dest_lib_file_path = dest_dir + static_lib_path.get_file();
+			Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path);
+			if (lib_copy_err != OK) {
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not copy a file at path \"%s\" to \"%s\"."), static_lib_path, dest_lib_file_path));
+				return lib_copy_err;
+			}
+		}
+	}
+
+	String iconset_dir = binary_dir + "/Images.xcassets/AppIcon.appiconset/";
+	err = OK;
+	if (!tmp_app_path->dir_exists(iconset_dir)) {
+		err = tmp_app_path->make_dir_recursive(iconset_dir);
+	}
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not create a directory at path \"%s\"."), iconset_dir));
+		return err;
+	}
+
+	err = _export_icons(p_preset, iconset_dir);
+	if (err != OK) {
+		// Message is supplied by the subroutine method.
+		return err;
+	}
+
+	{
+		String splash_image_path = binary_dir + "/Images.xcassets/SplashImage.imageset/";
+
+		Ref<DirAccess> launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+		if (launch_screen_da.is_null()) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not access the filesystem."));
+			return ERR_CANT_CREATE;
+		}
+
+		print_line("Exporting launch screen storyboard");
+
+		err = _export_loading_screen_file(p_preset, splash_image_path);
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), splash_image_path, err));
+		}
+	}
+
+	if (err != OK) {
+		return err;
+	}
+
+	print_line("Exporting additional assets");
+	_export_additional_assets(p_preset, binary_dir, libraries, assets);
+	_add_assets_to_project(dest_dir, p_preset, project_file_data, assets);
+	String project_file_name = binary_dir + ".xcodeproj/project.pbxproj";
+	{
+		Ref<FileAccess> f = FileAccess::open(project_file_name, FileAccess::WRITE);
+		if (f.is_null()) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write to a file at path \"%s\"."), project_file_name));
+			return ERR_CANT_CREATE;
+		};
+		f->store_buffer(project_file_data.ptr(), project_file_data.size());
+	}
+
+#ifdef MACOS_ENABLED
+	{
+		if (ep.step("Code-signing dylibs", 2)) {
+			return ERR_SKIP;
+		}
+		Ref<DirAccess> dylibs_dir = DirAccess::open(binary_dir + "/dylibs");
+		ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN);
+		CodesignData codesign_data(p_preset, p_debug);
+		err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
+			return err;
+		}
+	}
+
+	if (export_project_only) {
+		return OK;
+	}
+
+	if (ep.step("Making .xcarchive", 3)) {
+		return ERR_SKIP;
+	}
+
+	String archive_path = p_path.get_basename() + ".xcarchive";
+	List<String> archive_args;
+	archive_args.push_back("-project");
+	archive_args.push_back(binary_dir + ".xcodeproj");
+	archive_args.push_back("-scheme");
+	archive_args.push_back(binary_name);
+	archive_args.push_back("-sdk");
+	archive_args.push_back("iphoneos");
+	archive_args.push_back("-configuration");
+	archive_args.push_back(p_debug ? "Debug" : "Release");
+	archive_args.push_back("-destination");
+	archive_args.push_back("generic/platform=" + get_platform_name());
+	archive_args.push_back("archive");
+	archive_args.push_back("-allowProvisioningUpdates");
+	archive_args.push_back("-archivePath");
+	archive_args.push_back(archive_path);
+
+	String archive_str;
+	err = OS::get_singleton()->execute("xcodebuild", archive_args, &archive_str, nullptr, true);
+	if (err != OK) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err));
+		return err;
+	}
+
+	print_line("xcodebuild (.xcarchive):\n" + archive_str);
+	if (!archive_str.contains("** ARCHIVE SUCCEEDED **")) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR("Xcode project build failed, see editor log for details."));
+		return FAILED;
+	}
+
+	if (!p_oneclick) {
+		if (ep.step("Making .ipa", 4)) {
+			return ERR_SKIP;
+		}
+
+		List<String> export_args;
+		export_args.push_back("-exportArchive");
+		export_args.push_back("-archivePath");
+		export_args.push_back(archive_path);
+		export_args.push_back("-exportOptionsPlist");
+		export_args.push_back(binary_dir + "/export_options.plist");
+		export_args.push_back("-allowProvisioningUpdates");
+		export_args.push_back("-exportPath");
+		export_args.push_back(dest_dir);
+
+		String export_str;
+		err = OS::get_singleton()->execute("xcodebuild", export_args, &export_str, nullptr, true);
+		if (err != OK) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), vformat(TTR("Failed to run xcodebuild with code %d"), err));
+			return err;
+		}
+
+		print_line("xcodebuild (.ipa):\n" + export_str);
+		if (!export_str.contains("** EXPORT SUCCEEDED **")) {
+			add_message(EXPORT_MESSAGE_ERROR, TTR("Xcode Build"), TTR(".ipa export failed, see editor log for details."));
+			return FAILED;
+		}
+	}
+#else
+	add_message(EXPORT_MESSAGE_WARNING, TTR("Xcode Build"), TTR(".ipa can only be built on macOS. Leaving Xcode project without building the package."));
+#endif
+
+	return OK;
+}
+
+bool EditorExportPlatformAppleEmbedded::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+#if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED)
+	// TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore.
+	r_error += TTR("Exporting to an Apple Embedded platform when using C#/.NET is experimental and requires macOS.") + "\n";
+	return false;
+#else
+
+	String err;
+	bool valid = false;
+
+#if defined(MODULE_MONO_ENABLED)
+	// Apple Embedded export is still a work in progress, keep a message as a warning.
+	err += TTR("Exporting to an Apple Embedded platform when using C#/.NET is experimental.") + "\n";
+#endif
+	// Look for export templates (first official, and if defined custom templates).
+
+	bool dvalid = exists_export_template(get_platform_name() + ".zip", &err);
+	bool rvalid = dvalid; // Both in the same ZIP.
+
+	if (p_preset->get("custom_template/debug") != "") {
+		dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
+		if (!dvalid) {
+			err += TTR("Custom debug template not found.") + "\n";
+		}
+	}
+	if (p_preset->get("custom_template/release") != "") {
+		rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
+		if (!rvalid) {
+			err += TTR("Custom release template not found.") + "\n";
+		}
+	}
+
+	valid = dvalid || rvalid;
+	r_missing_templates = !valid;
+
+	const String &additional_plist_content = p_preset->get("application/additional_plist_content");
+	if (!additional_plist_content.is_empty()) {
+		const String &plist = vformat("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+									  "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+									  "<plist version=\"1.0\">"
+									  "<dict>\n"
+									  "%s\n"
+									  "</dict>\n"
+									  "</plist>\n",
+				additional_plist_content);
+
+		String plist_err;
+		Ref<PList> plist_parser;
+		plist_parser.instantiate();
+		if (!plist_parser->load_string(plist, plist_err)) {
+			err += TTR("Invalid additional PList content: ") + plist_err + "\n";
+			valid = false;
+		}
+	}
+
+	if (!err.is_empty()) {
+		r_error = err;
+	}
+
+	return valid;
+#endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED)
+}
+
+bool EditorExportPlatformAppleEmbedded::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+	String err;
+	bool valid = true;
+
+	// Validate the project configuration.
+
+	List<ExportOption> options;
+	get_export_options(&options);
+	for (const EditorExportPlatform::ExportOption &E : options) {
+		if (get_export_option_visibility(p_preset.ptr(), E.option.name)) {
+			String warn = get_export_option_warning(p_preset.ptr(), E.option.name);
+			if (!warn.is_empty()) {
+				err += warn + "\n";
+				if (E.required) {
+					valid = false;
+				}
+			}
+		}
+	}
+
+	if (!ResourceImporterTextureSettings::should_import_etc2_astc()) {
+		valid = false;
+	}
+
+	if (!err.is_empty()) {
+		r_error = err;
+	}
+
+	return valid;
+}
+
+int EditorExportPlatformAppleEmbedded::get_options_count() const {
+	MutexLock lock(device_lock);
+	return devices.size();
+}
+
+String EditorExportPlatformAppleEmbedded::get_options_tooltip() const {
+	return TTR("Select device from the list");
+}
+
+Ref<ImageTexture> EditorExportPlatformAppleEmbedded::get_option_icon(int p_index) const {
+	MutexLock lock(device_lock);
+
+	Ref<ImageTexture> icon;
+	if (p_index >= 0 || p_index < devices.size()) {
+		Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
+		if (theme.is_valid()) {
+			if (devices[p_index].wifi) {
+				icon = theme->get_icon("IOSDeviceWireless", EditorStringName(EditorIcons));
+			} else {
+				icon = theme->get_icon("IOSDeviceWired", EditorStringName(EditorIcons));
+			}
+		}
+	}
+	return icon;
+}
+
+String EditorExportPlatformAppleEmbedded::get_option_label(int p_index) const {
+	ERR_FAIL_INDEX_V(p_index, devices.size(), "");
+	MutexLock lock(device_lock);
+	return devices[p_index].name;
+}
+
+String EditorExportPlatformAppleEmbedded::get_option_tooltip(int p_index) const {
+	ERR_FAIL_INDEX_V(p_index, devices.size(), "");
+	MutexLock lock(device_lock);
+	return "UUID: " + devices[p_index].id;
+}
+
+bool EditorExportPlatformAppleEmbedded::is_package_name_valid(const String &p_package, String *r_error) const {
+	String pname = p_package;
+
+	if (pname.length() == 0) {
+		if (r_error) {
+			*r_error = TTR("Identifier is missing.");
+		}
+		return false;
+	}
+
+	for (int i = 0; i < pname.length(); i++) {
+		char32_t c = pname[i];
+		if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
+			if (r_error) {
+				*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
+			}
+			return false;
+		}
+	}
+
+	return true;
+}
+
+#ifdef MACOS_ENABLED
+bool EditorExportPlatformAppleEmbedded::_check_xcode_install() {
+	static bool xcode_found = false;
+	if (!xcode_found) {
+		Vector<String> mdfind_paths;
+		List<String> mdfind_args;
+		mdfind_args.push_back("kMDItemCFBundleIdentifier=com.apple.dt.Xcode");
+
+		String output;
+		Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
+		if (err == OK) {
+			mdfind_paths = output.split("\n");
+		}
+		for (const String &found_path : mdfind_paths) {
+			xcode_found = !found_path.is_empty() && DirAccess::dir_exists_absolute(found_path.strip_edges());
+			if (xcode_found) {
+				break;
+			}
+		}
+	}
+	return xcode_found;
+}
+
+void EditorExportPlatformAppleEmbedded::_check_for_changes_poll_thread(void *ud) {
+	EditorExportPlatformAppleEmbedded *ea = static_cast<EditorExportPlatformAppleEmbedded *>(ud);
+
+	while (!ea->quit_request.is_set()) {
+		// Nothing to do if we already know the plugins have changed.
+		if (!ea->plugins_changed.is_set()) {
+			MutexLock lock(ea->plugins_lock);
+
+			Vector<PluginConfigAppleEmbedded> loaded_plugins = get_plugins(ea->get_platform_name());
+
+			if (ea->plugins.size() != loaded_plugins.size()) {
+				ea->plugins_changed.set();
+			} else {
+				for (int i = 0; i < ea->plugins.size(); i++) {
+					if (ea->plugins[i].name != loaded_plugins[i].name || ea->plugins[i].last_updated != loaded_plugins[i].last_updated) {
+						ea->plugins_changed.set();
+						break;
+					}
+				}
+			}
+		}
+
+		// Check for devices updates.
+		Vector<Device> ldevices;
+
+		// Enum real devices (via ios_deploy, pre Xcode 15).
+		String ios_deploy_setting = "export/" + ea->get_platform_name() + "/ios_deploy";
+		if (EditorSettings::get_singleton()->has_setting(ios_deploy_setting)) {
+			String idepl = EDITOR_GET(ios_deploy_setting);
+			if (ea->has_runnable_preset.is_set() && !idepl.is_empty()) {
+				String devices;
+				List<String> args;
+				args.push_back("-c");
+				args.push_back("-timeout");
+				args.push_back("1");
+				args.push_back("-j");
+				args.push_back("-u");
+				args.push_back("-I");
+
+				int ec = 0;
+				Error err = OS::get_singleton()->execute(idepl, args, &devices, &ec, true);
+				if (err == OK && ec == 0) {
+					Ref<JSON> json;
+					json.instantiate();
+					devices = "{ \"devices\":[" + devices.replace("}{", "},{") + "]}";
+					err = json->parse(devices);
+					if (err == OK) {
+						Dictionary data = json->get_data();
+						Array devices = data["devices"];
+						for (int i = 0; i < devices.size(); i++) {
+							Dictionary device_event = devices[i];
+							if (device_event["Event"] == "DeviceDetected") {
+								Dictionary device_info = device_event["Device"];
+								Device nd;
+								nd.id = device_info["DeviceIdentifier"];
+								nd.name = device_info["DeviceName"].operator String() + " (ios_deploy, " + ((device_event["Interface"] == "WIFI") ? "network" : "wired") + ")";
+								nd.wifi = device_event["Interface"] == "WIFI";
+								nd.use_ios_deploy = true;
+								ldevices.push_back(nd);
+							}
+						}
+					}
+				}
+			}
+		}
+		// Enum devices (via Xcode).
+		if (ea->has_runnable_preset.is_set() && _check_xcode_install() && (FileAccess::exists("/usr/bin/xcrun") || FileAccess::exists("/bin/xcrun"))) {
+			String devices;
+			List<String> args;
+			args.push_back("devicectl");
+			args.push_back("list");
+			args.push_back("devices");
+			args.push_back("-j");
+			args.push_back("-");
+			args.push_back("-q");
+			int ec = 0;
+			Error err = OS::get_singleton()->execute("xcrun", args, &devices, &ec, true);
+			if (err == OK && ec == 0) {
+				Ref<JSON> json;
+				json.instantiate();
+				err = json->parse(devices);
+				if (err == OK) {
+					const Dictionary &data = json->get_data();
+					const Dictionary &result = data["result"];
+					const Array &devices = result["devices"];
+					for (int i = 0; i < devices.size(); i++) {
+						const Dictionary &device_info = devices[i];
+						const Dictionary &conn_props = device_info["connectionProperties"];
+						const Dictionary &dev_props = device_info["deviceProperties"];
+						if (conn_props["pairingState"] == "paired" && dev_props["developerModeStatus"] == "enabled") {
+							Device nd;
+							nd.id = device_info["identifier"];
+							nd.name = dev_props["name"].operator String() + " (devicectl, " + ((conn_props["transportType"] == "localNetwork") ? "network" : "wired") + ")";
+							nd.wifi = conn_props["transportType"] == "localNetwork";
+							ldevices.push_back(nd);
+						}
+					}
+				}
+			}
+		}
+
+		// Update device list.
+		{
+			MutexLock lock(ea->device_lock);
+
+			bool different = false;
+
+			if (ea->devices.size() != ldevices.size()) {
+				different = true;
+			} else {
+				for (int i = 0; i < ea->devices.size(); i++) {
+					if (ea->devices[i].id != ldevices[i].id) {
+						different = true;
+						break;
+					}
+				}
+			}
+
+			if (different) {
+				ea->devices = ldevices;
+				ea->devices_changed.set();
+			}
+		}
+
+		uint64_t sleep = 200;
+		uint64_t wait = 3000000;
+		uint64_t time = OS::get_singleton()->get_ticks_usec();
+		while (OS::get_singleton()->get_ticks_usec() - time < wait) {
+			OS::get_singleton()->delay_usec(1000 * sleep);
+			if (ea->quit_request.is_set()) {
+				break;
+			}
+		}
+	}
+}
+
+void EditorExportPlatformAppleEmbedded::_update_preset_status() {
+	const int preset_count = EditorExport::get_singleton()->get_export_preset_count();
+	bool has_runnable = false;
+
+	for (int i = 0; i < preset_count; i++) {
+		const Ref<EditorExportPreset> &preset = EditorExport::get_singleton()->get_export_preset(i);
+		if (preset->get_platform() == this && preset->is_runnable()) {
+			has_runnable = true;
+			break;
+		}
+	}
+
+	if (has_runnable) {
+		has_runnable_preset.set();
+	} else {
+		has_runnable_preset.clear();
+	}
+	devices_changed.set();
+}
+#endif
+
+Error EditorExportPlatformAppleEmbedded::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
+#ifdef MACOS_ENABLED
+	ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
+
+	String can_export_error;
+	bool can_export_missing_templates;
+	if (!can_export(p_preset, can_export_error, can_export_missing_templates)) {
+		add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), can_export_error);
+		return ERR_UNCONFIGURED;
+	}
+
+	MutexLock lock(device_lock);
+
+	EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3);
+
+	String id = "tmpexport." + uitos(OS::get_singleton()->get_unix_time());
+
+	Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + EditorPaths::get_singleton()->get_temp_dir() + "'.");
+	filesystem_da->make_dir_recursive(EditorPaths::get_singleton()->get_temp_dir().path_join(id));
+	String tmp_export_path = EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.ipa");
+
+#define CLEANUP_AND_RETURN(m_err)                                                                          \
+	{                                                                                                      \
+		if (filesystem_da->change_dir(EditorPaths::get_singleton()->get_temp_dir().path_join(id)) == OK) { \
+			filesystem_da->erase_contents_recursive();                                                     \
+			filesystem_da->change_dir("..");                                                               \
+			filesystem_da->remove(id);                                                                     \
+		}                                                                                                  \
+		return m_err;                                                                                      \
+	}                                                                                                      \
+	((void)0)
+
+	Device dev = devices[p_device];
+
+	// Export before sending to device.
+	Error err = _export_project_helper(p_preset, true, tmp_export_path, p_debug_flags, true);
+
+	if (err != OK) {
+		CLEANUP_AND_RETURN(err);
+	}
+
+	Vector<String> cmd_args_list;
+	String host = EDITOR_GET("network/debug/remote_host");
+	int remote_port = (int)EDITOR_GET("network/debug/remote_port");
+
+	if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
+		host = "localhost";
+	}
+
+	if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
+		int port = EDITOR_GET("filesystem/file_server/port");
+		String passwd = EDITOR_GET("filesystem/file_server/password");
+		cmd_args_list.push_back("--remote-fs");
+		cmd_args_list.push_back(host + ":" + itos(port));
+		if (!passwd.is_empty()) {
+			cmd_args_list.push_back("--remote-fs-password");
+			cmd_args_list.push_back(passwd);
+		}
+	}
+
+	if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
+		cmd_args_list.push_back("--remote-debug");
+
+		cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num_int64(remote_port));
+
+		List<String> breakpoints;
+		ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
+
+		if (breakpoints.size()) {
+			cmd_args_list.push_back("--breakpoints");
+			String bpoints;
+			for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
+				bpoints += E->get().replace(" ", "%20");
+				if (E->next()) {
+					bpoints += ",";
+				}
+			}
+
+			cmd_args_list.push_back(bpoints);
+		}
+	}
+
+	if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
+		cmd_args_list.push_back("--debug-collisions");
+	}
+
+	if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
+		cmd_args_list.push_back("--debug-navigation");
+	}
+
+	if (dev.use_ios_deploy) {
+		// Deploy and run on real device (via ios-deploy).
+		if (ep.step("Installing and running on device...", 4)) {
+			CLEANUP_AND_RETURN(ERR_SKIP);
+		} else {
+			List<String> args;
+			args.push_back("-u");
+			args.push_back("-I");
+			args.push_back("--id");
+			args.push_back(dev.id);
+			args.push_back("--justlaunch");
+			args.push_back("--bundle");
+			args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app"));
+			String app_args;
+			for (const String &E : cmd_args_list) {
+				app_args += E + " ";
+			}
+			if (!app_args.is_empty()) {
+				args.push_back("--args");
+				args.push_back(app_args);
+			}
+
+			String idepl = EDITOR_GET("export/" + get_platform_name() + "/ios_deploy");
+			if (idepl.is_empty()) {
+				idepl = "ios-deploy";
+			}
+			String log;
+			int ec;
+			err = OS::get_singleton()->execute(idepl, args, &log, &ec, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start ios-deploy executable."));
+				CLEANUP_AND_RETURN(err);
+			}
+			if (ec != 0) {
+				print_line("ios-deploy:\n" + log);
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation/running failed, see editor log for details."));
+				CLEANUP_AND_RETURN(ERR_UNCONFIGURED);
+			}
+		}
+	} else {
+		// Deploy and run on real device (via Xcode).
+		if (ep.step("Installing to device...", 3)) {
+			CLEANUP_AND_RETURN(ERR_SKIP);
+		} else {
+			List<String> args;
+			args.push_back("devicectl");
+			args.push_back("device");
+			args.push_back("install");
+			args.push_back("app");
+			args.push_back("-d");
+			args.push_back(dev.id);
+			args.push_back(EditorPaths::get_singleton()->get_temp_dir().path_join(id).path_join("export.xcarchive/Products/Applications/export.app"));
+
+			String log;
+			int ec;
+			err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start device executable."));
+				CLEANUP_AND_RETURN(err);
+			}
+			if (ec != 0) {
+				print_line("device install:\n" + log);
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Installation failed, see editor log for details."));
+				CLEANUP_AND_RETURN(ERR_UNCONFIGURED);
+			}
+		}
+
+		if (ep.step("Running on device...", 4)) {
+			CLEANUP_AND_RETURN(ERR_SKIP);
+		} else {
+			List<String> args;
+			args.push_back("devicectl");
+			args.push_back("device");
+			args.push_back("process");
+			args.push_back("launch");
+			args.push_back("--terminate-existing");
+			args.push_back("-d");
+			args.push_back(dev.id);
+			args.push_back(p_preset->get("application/bundle_identifier"));
+			for (const String &E : cmd_args_list) {
+				args.push_back(E);
+			}
+
+			String log;
+			int ec;
+			err = OS::get_singleton()->execute("xcrun", args, &log, &ec, true);
+			if (err != OK) {
+				add_message(EXPORT_MESSAGE_WARNING, TTR("Run"), TTR("Could not start devicectl executable."));
+				CLEANUP_AND_RETURN(err);
+			}
+			if (ec != 0) {
+				print_line("devicectl launch:\n" + log);
+				add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Running failed, see editor log for details."));
+			}
+		}
+	}
+
+	CLEANUP_AND_RETURN(OK);
+
+#undef CLEANUP_AND_RETURN
+#else
+	return ERR_UNCONFIGURED;
+#endif
+}
+
+EditorExportPlatformAppleEmbedded::EditorExportPlatformAppleEmbedded(const char *p_platform_logo_svg, const char *p_run_icon_svg) {
+	if (EditorNode::get_singleton()) {
+		Ref<Image> img = memnew(Image);
+		const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
+
+		ImageLoaderSVG::create_image_from_string(img, p_platform_logo_svg, EDSCALE, upsample, false);
+		logo = ImageTexture::create_from_image(img);
+
+		ImageLoaderSVG::create_image_from_string(img, p_run_icon_svg, EDSCALE, upsample, false);
+		run_icon = ImageTexture::create_from_image(img);
+
+		plugins_changed.set();
+		devices_changed.set();
+#ifdef MACOS_ENABLED
+		_update_preset_status();
+		check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+#endif
+	}
+}
+
+EditorExportPlatformAppleEmbedded::~EditorExportPlatformAppleEmbedded() {
+#ifdef MACOS_ENABLED
+	quit_request.set();
+	if (check_for_changes_thread.is_started()) {
+		check_for_changes_thread.wait_to_finish();
+	}
+#endif
+}

+ 316 - 0
editor/export/editor_export_platform_apple_embedded.h

@@ -0,0 +1,316 @@
+/**************************************************************************/
+/*  editor_export_platform_apple_embedded.h                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include "plugin_config_apple_embedded.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/file_access.h"
+#include "core/io/image_loader.h"
+#include "core/io/marshalls.h"
+#include "core/io/resource_saver.h"
+#include "core/io/zip_io.h"
+#include "core/os/os.h"
+#include "core/templates/safe_refcount.h"
+#include "editor/editor_settings.h"
+#include "editor/export/editor_export_platform.h"
+#include "main/splash.gen.h"
+#include "scene/resources/image_texture.h"
+
+#include <sys/stat.h>
+
+// Optional environment variables for defining confidential information. If any
+// of these is set, they will override the values set in the credentials file.
+const String ENV_APPLE_PLATFORM_PROFILE_UUID_DEBUG = "GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_DEBUG";
+const String ENV_APPLE_PLATFORM_PROFILE_UUID_RELEASE = "GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_RELEASE";
+const String ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG = "GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG";
+const String ENV_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE = "GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE";
+
+static const String storyboard_image_scale_mode[] = {
+	"center",
+	"scaleAspectFit",
+	"scaleAspectFill",
+	"scaleToFill",
+};
+
+class EditorExportPlatformAppleEmbedded : public EditorExportPlatform {
+	GDCLASS(EditorExportPlatformAppleEmbedded, EditorExportPlatform);
+
+	Ref<ImageTexture> logo;
+	Ref<ImageTexture> run_icon;
+
+	// Plugins
+	mutable SafeFlag plugins_changed;
+	SafeFlag devices_changed;
+
+	struct Device {
+		String id;
+		String name;
+		bool wifi = false;
+		bool use_ios_deploy = false;
+	};
+
+	Vector<Device> devices;
+	Mutex device_lock;
+
+	Mutex plugins_lock;
+	mutable Vector<PluginConfigAppleEmbedded> plugins;
+#ifdef MACOS_ENABLED
+	Thread check_for_changes_thread;
+	SafeFlag quit_request;
+	SafeFlag has_runnable_preset;
+
+	static bool _check_xcode_install();
+	static void _check_for_changes_poll_thread(void *ud);
+	void _update_preset_status();
+#endif
+
+	typedef Error (*FileHandler)(String p_file, void *p_userdata);
+	static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata);
+	static Error _codesign(String p_file, void *p_userdata);
+
+	struct AppleEmbeddedConfigData {
+		String pkg_name;
+		String binary_name;
+		String plist_content;
+		String architectures;
+		String linker_flags;
+		String cpp_code;
+		String modules_buildfile;
+		String modules_fileref;
+		String modules_buildphase;
+		String modules_buildgrp;
+		Vector<String> capabilities;
+		bool use_swift_runtime;
+	};
+
+	struct ExportArchitecture {
+		String name;
+		bool is_default = false;
+
+		ExportArchitecture() {}
+
+		ExportArchitecture(String p_name, bool p_is_default) {
+			name = p_name;
+			is_default = p_is_default;
+		}
+	};
+
+	struct AppleEmbeddedExportAsset {
+		String exported_path;
+		bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource
+		bool should_embed = false;
+	};
+
+	String _get_additional_plist_content();
+	String _get_linker_flags();
+	String _get_cpp_code();
+	void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const AppleEmbeddedConfigData &p_config, bool p_debug);
+
+	Vector<ExportArchitecture> _get_supported_architectures() const;
+	Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const;
+
+	void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const;
+	Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const;
+
+	void _add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<AppleEmbeddedExportAsset> &p_additional_assets);
+	Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<AppleEmbeddedExportAsset> &r_exported_assets);
+	Error _copy_asset(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<AppleEmbeddedExportAsset> &r_exported_assets);
+	Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<AppleEmbeddedExportAsset> &r_exported_assets);
+	Error _export_apple_embedded_plugins(const Ref<EditorExportPreset> &p_preset, AppleEmbeddedConfigData &p_config_data, const String &dest_dir, Vector<AppleEmbeddedExportAsset> &r_exported_assets, bool p_debug);
+
+	Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_oneclick);
+
+	bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
+
+protected:
+	struct IconInfo {
+		const char *preset_key;
+		const char *idiom;
+		const char *export_name;
+		const char *actual_size_side;
+		const char *scale;
+		const char *unscaled_size;
+		bool force_opaque;
+	};
+
+	void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
+
+	virtual Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { return OK; }
+	virtual Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) { return OK; }
+
+	virtual String get_platform_name() const = 0;
+	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
+	virtual void get_export_options(List<ExportOption> *r_options) const override;
+	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
+	virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
+
+	virtual Vector<IconInfo> get_icon_infos() const = 0;
+
+	void _notification(int p_what);
+
+	virtual void get_platform_features(List<String> *r_features) const override {
+		r_features->push_back("mobile");
+		r_features->push_back("apple_embedded");
+	}
+
+public:
+	virtual Ref<Texture2D> get_logo() const override { return logo; }
+	virtual Ref<Texture2D> get_run_icon() const override { return run_icon; }
+
+	virtual int get_options_count() const override;
+	virtual String get_options_tooltip() const override;
+	virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+	virtual String get_option_label(int p_index) const override;
+	virtual String get_option_tooltip(int p_index) const override;
+	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
+
+	virtual bool poll_export() override {
+		bool dc = devices_changed.is_set();
+		if (dc) {
+			// don't clear unless we're reporting true, to avoid race
+			devices_changed.clear();
+		}
+		return dc;
+	}
+
+	virtual bool should_update_export_options() override {
+		bool export_options_changed = plugins_changed.is_set();
+		if (export_options_changed) {
+			// don't clear unless we're reporting true, to avoid race
+			plugins_changed.clear();
+		}
+		return export_options_changed;
+	}
+
+	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
+		List<String> list;
+		if (p_preset.is_valid()) {
+			bool project_only = p_preset->get("application/export_project_only");
+			if (project_only) {
+				list.push_back("xcodeproj");
+			} else {
+				list.push_back("ipa");
+			}
+		}
+		return list;
+	}
+
+	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+
+	virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+	virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+
+	virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {
+	}
+
+	EditorExportPlatformAppleEmbedded(const char *p_platform_logo_svg, const char *p_run_icon_svg);
+	~EditorExportPlatformAppleEmbedded();
+
+	/// List the gdip files in the directory specified by the p_path parameter.
+	static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
+		Vector<String> dir_files;
+		Ref<DirAccess> da = DirAccess::open(p_path);
+		if (da.is_valid()) {
+			da->list_dir_begin();
+			while (true) {
+				String file = da->get_next();
+				if (file.is_empty()) {
+					break;
+				}
+
+				if (file == "." || file == "..") {
+					continue;
+				}
+
+				if (da->current_is_hidden()) {
+					continue;
+				}
+
+				if (da->current_is_dir()) {
+					if (p_check_directories) {
+						Vector<String> directory_files = list_plugin_config_files(p_path.path_join(file), false);
+						for (int i = 0; i < directory_files.size(); ++i) {
+							dir_files.push_back(file.path_join(directory_files[i]));
+						}
+					}
+
+					continue;
+				}
+
+				if (file.ends_with(PluginConfigAppleEmbedded::PLUGIN_CONFIG_EXT)) {
+					dir_files.push_back(file);
+				}
+			}
+			da->list_dir_end();
+		}
+
+		return dir_files;
+	}
+
+	static Vector<PluginConfigAppleEmbedded> get_plugins(const String &p_platform_name) {
+		Vector<PluginConfigAppleEmbedded> loaded_plugins;
+
+		String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().path_join(p_platform_name + "/plugins");
+
+		if (DirAccess::exists(plugins_dir)) {
+			Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
+
+			if (!plugins_filenames.is_empty()) {
+				Ref<ConfigFile> config_file;
+				for (int i = 0; i < plugins_filenames.size(); i++) {
+					PluginConfigAppleEmbedded config = PluginConfigAppleEmbedded::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i]));
+					if (config.valid_config) {
+						loaded_plugins.push_back(config);
+					} else {
+						print_error("Invalid plugin config file " + plugins_filenames[i]);
+					}
+				}
+			}
+		}
+
+		return loaded_plugins;
+	}
+
+	static Vector<PluginConfigAppleEmbedded> get_enabled_plugins(const String &p_platform_name, const Ref<EditorExportPreset> &p_presets) {
+		Vector<PluginConfigAppleEmbedded> enabled_plugins;
+		Vector<PluginConfigAppleEmbedded> all_plugins = get_plugins(p_platform_name);
+		for (int i = 0; i < all_plugins.size(); i++) {
+			PluginConfigAppleEmbedded plugin = all_plugins[i];
+			bool enabled = p_presets->get("plugins/" + plugin.name);
+			if (enabled) {
+				enabled_plugins.push_back(plugin);
+			}
+		}
+
+		return enabled_plugins;
+	}
+};

+ 49 - 37
editor/export/editor_export_plugin.cpp

@@ -67,55 +67,55 @@ void EditorExportPlugin::_add_shared_object(const SharedObject &p_shared_object)
 	shared_objects.push_back(p_shared_object);
 }
 
-void EditorExportPlugin::add_ios_framework(const String &p_path) {
-	ios_frameworks.push_back(p_path);
+void EditorExportPlugin::add_apple_embedded_platform_framework(const String &p_path) {
+	apple_embedded_platform_frameworks.push_back(p_path);
 }
 
-void EditorExportPlugin::add_ios_embedded_framework(const String &p_path) {
-	ios_embedded_frameworks.push_back(p_path);
+void EditorExportPlugin::add_apple_embedded_platform_embedded_framework(const String &p_path) {
+	apple_embedded_platform_embedded_frameworks.push_back(p_path);
 }
 
-Vector<String> EditorExportPlugin::get_ios_frameworks() const {
-	return ios_frameworks;
+Vector<String> EditorExportPlugin::get_apple_embedded_platform_frameworks() const {
+	return apple_embedded_platform_frameworks;
 }
 
-Vector<String> EditorExportPlugin::get_ios_embedded_frameworks() const {
-	return ios_embedded_frameworks;
+Vector<String> EditorExportPlugin::get_apple_embedded_platform_embedded_frameworks() const {
+	return apple_embedded_platform_embedded_frameworks;
 }
 
-void EditorExportPlugin::add_ios_plist_content(const String &p_plist_content) {
-	ios_plist_content += p_plist_content + "\n";
+void EditorExportPlugin::add_apple_embedded_platform_plist_content(const String &p_plist_content) {
+	apple_embedded_platform_plist_content += p_plist_content + "\n";
 }
 
-String EditorExportPlugin::get_ios_plist_content() const {
-	return ios_plist_content;
+String EditorExportPlugin::get_apple_embedded_platform_plist_content() const {
+	return apple_embedded_platform_plist_content;
 }
 
-void EditorExportPlugin::add_ios_linker_flags(const String &p_flags) {
-	if (ios_linker_flags.length() > 0) {
-		ios_linker_flags += ' ';
+void EditorExportPlugin::add_apple_embedded_platform_linker_flags(const String &p_flags) {
+	if (apple_embedded_platform_linker_flags.length() > 0) {
+		apple_embedded_platform_linker_flags += ' ';
 	}
-	ios_linker_flags += p_flags;
+	apple_embedded_platform_linker_flags += p_flags;
 }
 
-String EditorExportPlugin::get_ios_linker_flags() const {
-	return ios_linker_flags;
+String EditorExportPlugin::get_apple_embedded_platform_linker_flags() const {
+	return apple_embedded_platform_linker_flags;
 }
 
-void EditorExportPlugin::add_ios_bundle_file(const String &p_path) {
-	ios_bundle_files.push_back(p_path);
+void EditorExportPlugin::add_apple_embedded_platform_bundle_file(const String &p_path) {
+	apple_embedded_platform_bundle_files.push_back(p_path);
 }
 
-Vector<String> EditorExportPlugin::get_ios_bundle_files() const {
-	return ios_bundle_files;
+Vector<String> EditorExportPlugin::get_apple_embedded_platform_bundle_files() const {
+	return apple_embedded_platform_bundle_files;
 }
 
-void EditorExportPlugin::add_ios_cpp_code(const String &p_code) {
-	ios_cpp_code += p_code;
+void EditorExportPlugin::add_apple_embedded_platform_cpp_code(const String &p_code) {
+	apple_embedded_platform_cpp_code += p_code;
 }
 
-String EditorExportPlugin::get_ios_cpp_code() const {
-	return ios_cpp_code;
+String EditorExportPlugin::get_apple_embedded_platform_cpp_code() const {
+	return apple_embedded_platform_cpp_code;
 }
 
 void EditorExportPlugin::add_macos_plugin_file(const String &p_path) {
@@ -126,12 +126,12 @@ const Vector<String> &EditorExportPlugin::get_macos_plugin_files() const {
 	return macos_plugin_files;
 }
 
-void EditorExportPlugin::add_ios_project_static_lib(const String &p_path) {
-	ios_project_static_libs.push_back(p_path);
+void EditorExportPlugin::add_apple_embedded_platform_project_static_lib(const String &p_path) {
+	apple_embedded_platform_project_static_libs.push_back(p_path);
 }
 
-Vector<String> EditorExportPlugin::get_ios_project_static_libs() const {
-	return ios_project_static_libs;
+Vector<String> EditorExportPlugin::get_apple_embedded_platform_project_static_libs() const {
+	return apple_embedded_platform_project_static_libs;
 }
 
 Variant EditorExportPlugin::get_option(const StringName &p_name) const {
@@ -328,14 +328,26 @@ void EditorExportPlugin::skip() {
 
 void EditorExportPlugin::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags", "target"), &EditorExportPlugin::add_shared_object);
-	ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib);
 	ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file);
-	ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework);
-	ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_ios_embedded_framework);
-	ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content);
-	ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_ios_linker_flags);
-	ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file);
-	ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code);
+
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_project_static_lib", "path"), &EditorExportPlugin::add_apple_embedded_platform_project_static_lib);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_framework);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_embedded_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_embedded_framework);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_plist_content", "plist_content"), &EditorExportPlugin::add_apple_embedded_platform_plist_content);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_linker_flags", "flags"), &EditorExportPlugin::add_apple_embedded_platform_linker_flags);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_bundle_file", "path"), &EditorExportPlugin::add_apple_embedded_platform_bundle_file);
+	ClassDB::bind_method(D_METHOD("add_apple_embedded_platform_cpp_code", "code"), &EditorExportPlugin::add_apple_embedded_platform_cpp_code);
+
+#ifndef DISABLE_DEPRECATED
+	ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_apple_embedded_platform_project_static_lib);
+	ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_framework);
+	ClassDB::bind_method(D_METHOD("add_ios_embedded_framework", "path"), &EditorExportPlugin::add_apple_embedded_platform_embedded_framework);
+	ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_apple_embedded_platform_plist_content);
+	ClassDB::bind_method(D_METHOD("add_ios_linker_flags", "flags"), &EditorExportPlugin::add_apple_embedded_platform_linker_flags);
+	ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_apple_embedded_platform_bundle_file);
+	ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_apple_embedded_platform_cpp_code);
+#endif
+
 	ClassDB::bind_method(D_METHOD("add_macos_plugin_file", "path"), &EditorExportPlugin::add_macos_plugin_file);
 	ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
 	ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option);

+ 27 - 27
editor/export/editor_export_plugin.h

@@ -53,13 +53,13 @@ class EditorExportPlugin : public RefCounted {
 	Vector<ExtraFile> extra_files;
 	bool skipped = false;
 
-	Vector<String> ios_frameworks;
-	Vector<String> ios_embedded_frameworks;
-	Vector<String> ios_project_static_libs;
-	String ios_plist_content;
-	String ios_linker_flags;
-	Vector<String> ios_bundle_files;
-	String ios_cpp_code;
+	Vector<String> apple_embedded_platform_frameworks;
+	Vector<String> apple_embedded_platform_embedded_frameworks;
+	Vector<String> apple_embedded_platform_project_static_libs;
+	String apple_embedded_platform_plist_content;
+	String apple_embedded_platform_linker_flags;
+	Vector<String> apple_embedded_platform_bundle_files;
+	String apple_embedded_platform_cpp_code;
 
 	Vector<String> macos_plugin_files;
 
@@ -70,12 +70,12 @@ class EditorExportPlugin : public RefCounted {
 	}
 
 	_FORCE_INLINE_ void _export_end_clear() {
-		ios_frameworks.clear();
-		ios_embedded_frameworks.clear();
-		ios_bundle_files.clear();
-		ios_plist_content = "";
-		ios_linker_flags = "";
-		ios_cpp_code = "";
+		apple_embedded_platform_frameworks.clear();
+		apple_embedded_platform_embedded_frameworks.clear();
+		apple_embedded_platform_bundle_files.clear();
+		apple_embedded_platform_plist_content = "";
+		apple_embedded_platform_linker_flags = "";
+		apple_embedded_platform_cpp_code = "";
 		macos_plugin_files.clear();
 	}
 
@@ -95,13 +95,13 @@ protected:
 	void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
 	void _add_shared_object(const SharedObject &p_shared_object);
 
-	void add_ios_framework(const String &p_path);
-	void add_ios_embedded_framework(const String &p_path);
-	void add_ios_project_static_lib(const String &p_path);
-	void add_ios_plist_content(const String &p_plist_content);
-	void add_ios_linker_flags(const String &p_flags);
-	void add_ios_bundle_file(const String &p_path);
-	void add_ios_cpp_code(const String &p_code);
+	void add_apple_embedded_platform_framework(const String &p_path);
+	void add_apple_embedded_platform_embedded_framework(const String &p_path);
+	void add_apple_embedded_platform_project_static_lib(const String &p_path);
+	void add_apple_embedded_platform_plist_content(const String &p_plist_content);
+	void add_apple_embedded_platform_linker_flags(const String &p_flags);
+	void add_apple_embedded_platform_bundle_file(const String &p_path);
+	void add_apple_embedded_platform_cpp_code(const String &p_code);
 	void add_macos_plugin_file(const String &p_path);
 
 	void skip();
@@ -177,13 +177,13 @@ public:
 	virtual String get_android_manifest_element_contents(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const;
 	virtual PackedByteArray update_android_prebuilt_manifest(const Ref<EditorExportPlatform> &p_export_platform, const PackedByteArray &p_manifest_data) const;
 
-	Vector<String> get_ios_frameworks() const;
-	Vector<String> get_ios_embedded_frameworks() const;
-	Vector<String> get_ios_project_static_libs() const;
-	String get_ios_plist_content() const;
-	String get_ios_linker_flags() const;
-	Vector<String> get_ios_bundle_files() const;
-	String get_ios_cpp_code() const;
+	Vector<String> get_apple_embedded_platform_frameworks() const;
+	Vector<String> get_apple_embedded_platform_embedded_frameworks() const;
+	Vector<String> get_apple_embedded_platform_project_static_libs() const;
+	String get_apple_embedded_platform_plist_content() const;
+	String get_apple_embedded_platform_linker_flags() const;
+	Vector<String> get_apple_embedded_platform_bundle_files() const;
+	String get_apple_embedded_platform_cpp_code() const;
 	const Vector<String> &get_macos_plugin_files() const;
 	Variant get_option(const StringName &p_name) const;
 };

+ 44 - 44
platform/ios/export/godot_plugin_config.cpp → editor/export/plugin_config_apple_embedded.cpp

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_plugin_config.cpp                                               */
+/*  plugin_config_apple_embedded.cpp                                      */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,13 +28,13 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#include "godot_plugin_config.h"
+#include "plugin_config_apple_embedded.h"
 
 #include "core/config/project_settings.h"
 #include "core/io/dir_access.h"
 #include "core/io/file_access.h"
 
-String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+String PluginConfigAppleEmbedded::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
 	String absolute_path;
 
 	if (dependency_path.is_empty()) {
@@ -51,7 +51,7 @@ String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir,
 	return absolute_path.replace(res_path, "res://");
 }
 
-String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) {
+String PluginConfigAppleEmbedded::resolve_system_dependency_path(String dependency_path) {
 	String absolute_path;
 
 	if (dependency_path.is_empty()) {
@@ -67,7 +67,7 @@ String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) {
 	return system_path.path_join(dependency_path);
 }
 
-Vector<String> PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
+Vector<String> PluginConfigAppleEmbedded::resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) {
 	Vector<String> paths;
 
 	for (int i = 0; i < p_paths.size(); i++) {
@@ -83,7 +83,7 @@ Vector<String> PluginConfigIOS::resolve_local_dependencies(String plugin_config_
 	return paths;
 }
 
-Vector<String> PluginConfigIOS::resolve_system_dependencies(Vector<String> p_paths) {
+Vector<String> PluginConfigAppleEmbedded::resolve_system_dependencies(Vector<String> p_paths) {
 	Vector<String> paths;
 
 	for (int i = 0; i < p_paths.size(); i++) {
@@ -99,7 +99,7 @@ Vector<String> PluginConfigIOS::resolve_system_dependencies(Vector<String> p_pat
 	return paths;
 }
 
-bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) {
+bool PluginConfigAppleEmbedded::validate_plugin(PluginConfigAppleEmbedded &plugin_config) {
 	bool valid_name = !plugin_config.name.is_empty();
 	bool valid_binary_name = !plugin_config.binary.is_empty();
 	bool valid_initialize = !plugin_config.initialization_method.is_empty();
@@ -134,7 +134,7 @@ bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) {
 	return plugin_config.valid_config;
 }
 
-String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug) {
+String PluginConfigAppleEmbedded::get_plugin_main_binary(PluginConfigAppleEmbedded &plugin_config, bool p_debug) {
 	if (!plugin_config.supports_targets) {
 		return plugin_config.binary;
 	}
@@ -147,7 +147,7 @@ String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, b
 	return plugin_binary_dir.path_join(plugin_file);
 }
 
-uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) {
+uint64_t PluginConfigAppleEmbedded::get_plugin_modification_time(const PluginConfigAppleEmbedded &plugin_config, const String &config_path) {
 	uint64_t last_updated = FileAccess::get_modified_time(config_path);
 
 	if (!plugin_config.supports_targets) {
@@ -166,8 +166,8 @@ uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &pl
 	return last_updated;
 }
 
-PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
-	PluginConfigIOS plugin_config = {};
+PluginConfigAppleEmbedded PluginConfigAppleEmbedded::load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+	PluginConfigAppleEmbedded plugin_config = {};
 
 	if (config_file.is_null()) {
 		return plugin_config;
@@ -183,19 +183,19 @@ PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref<ConfigFile> config_file,
 
 	String config_base_dir = path.get_base_dir();
 
-	plugin_config.name = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_NAME_KEY, String());
-	plugin_config.use_swift_runtime = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_USE_SWIFT_KEY, false);
-	plugin_config.initialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_INITIALIZE_KEY, String());
-	plugin_config.deinitialization_method = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_DEINITIALIZE_KEY, String());
+	plugin_config.name = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_NAME_KEY, String());
+	plugin_config.use_swift_runtime = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_USE_SWIFT_KEY, false);
+	plugin_config.initialization_method = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_INITIALIZE_KEY, String());
+	plugin_config.deinitialization_method = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_DEINITIALIZE_KEY, String());
 
-	String binary_path = config_file->get_value(PluginConfigIOS::CONFIG_SECTION, PluginConfigIOS::CONFIG_BINARY_KEY, String());
+	String binary_path = config_file->get_value(PluginConfigAppleEmbedded::CONFIG_SECTION, PluginConfigAppleEmbedded::CONFIG_BINARY_KEY, String());
 	plugin_config.binary = resolve_local_dependency_path(config_base_dir, binary_path);
 
-	if (config_file->has_section(PluginConfigIOS::DEPENDENCIES_SECTION)) {
-		Vector<String> linked_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKED_KEY, Vector<String>());
-		Vector<String> embedded_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
-		Vector<String> system_dependencies = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_SYSTEM_KEY, Vector<String>());
-		Vector<String> files = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_FILES_KEY, Vector<String>());
+	if (config_file->has_section(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION)) {
+		Vector<String> linked_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_LINKED_KEY, Vector<String>());
+		Vector<String> embedded_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_EMBEDDED_KEY, Vector<String>());
+		Vector<String> system_dependencies = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_SYSTEM_KEY, Vector<String>());
+		Vector<String> files = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_FILES_KEY, Vector<String>());
 
 		plugin_config.linked_dependencies = resolve_local_dependencies(config_base_dir, linked_dependencies);
 		plugin_config.embedded_dependencies = resolve_local_dependencies(config_base_dir, embedded_dependencies);
@@ -203,77 +203,77 @@ PluginConfigIOS PluginConfigIOS::load_plugin_config(Ref<ConfigFile> config_file,
 
 		plugin_config.files_to_copy = resolve_local_dependencies(config_base_dir, files);
 
-		plugin_config.capabilities = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
+		plugin_config.capabilities = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_CAPABILITIES_KEY, Vector<String>());
 
-		plugin_config.linker_flags = config_file->get_value(PluginConfigIOS::DEPENDENCIES_SECTION, PluginConfigIOS::DEPENDENCIES_LINKER_FLAGS, Vector<String>());
+		plugin_config.linker_flags = config_file->get_value(PluginConfigAppleEmbedded::DEPENDENCIES_SECTION, PluginConfigAppleEmbedded::DEPENDENCIES_LINKER_FLAGS, Vector<String>());
 	}
 
-	if (config_file->has_section(PluginConfigIOS::PLIST_SECTION)) {
-		Vector<String> keys = config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION);
+	if (config_file->has_section(PluginConfigAppleEmbedded::PLIST_SECTION)) {
+		Vector<String> keys = config_file->get_section_keys(PluginConfigAppleEmbedded::PLIST_SECTION);
 
 		for (const String &key : keys) {
 			Vector<String> key_components = key.split(":");
 
 			String key_value = "";
-			PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN;
+			PluginConfigAppleEmbedded::PlistItemType key_type = PluginConfigAppleEmbedded::PlistItemType::UNKNOWN;
 
 			if (key_components.size() == 1) {
 				key_value = key_components[0];
-				key_type = PluginConfigIOS::PlistItemType::STRING;
+				key_type = PluginConfigAppleEmbedded::PlistItemType::STRING;
 			} else if (key_components.size() == 2) {
 				key_value = key_components[0];
 
 				if (key_components[1].to_lower() == "string") {
-					key_type = PluginConfigIOS::PlistItemType::STRING;
+					key_type = PluginConfigAppleEmbedded::PlistItemType::STRING;
 				} else if (key_components[1].to_lower() == "integer") {
-					key_type = PluginConfigIOS::PlistItemType::INTEGER;
+					key_type = PluginConfigAppleEmbedded::PlistItemType::INTEGER;
 				} else if (key_components[1].to_lower() == "boolean") {
-					key_type = PluginConfigIOS::PlistItemType::BOOLEAN;
+					key_type = PluginConfigAppleEmbedded::PlistItemType::BOOLEAN;
 				} else if (key_components[1].to_lower() == "raw") {
-					key_type = PluginConfigIOS::PlistItemType::RAW;
+					key_type = PluginConfigAppleEmbedded::PlistItemType::RAW;
 				} else if (key_components[1].to_lower() == "string_input") {
-					key_type = PluginConfigIOS::PlistItemType::STRING_INPUT;
+					key_type = PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT;
 				}
 			}
 
-			if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) {
+			if (key_value.is_empty() || key_type == PluginConfigAppleEmbedded::PlistItemType::UNKNOWN) {
 				continue;
 			}
 
 			String value;
 
 			switch (key_type) {
-				case PluginConfigIOS::PlistItemType::STRING: {
-					String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String());
+				case PluginConfigAppleEmbedded::PlistItemType::STRING: {
+					String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String());
 					value = "<string>" + raw_value + "</string>";
 				} break;
-				case PluginConfigIOS::PlistItemType::INTEGER: {
-					int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, 0);
+				case PluginConfigAppleEmbedded::PlistItemType::INTEGER: {
+					int raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, 0);
 					Dictionary value_dictionary;
 					String value_format = "<integer>$value</integer>";
 					value_dictionary["value"] = raw_value;
 					value = value_format.format(value_dictionary, "$_");
 				} break;
-				case PluginConfigIOS::PlistItemType::BOOLEAN:
-					if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, false)) {
+				case PluginConfigAppleEmbedded::PlistItemType::BOOLEAN:
+					if (config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, false)) {
 						value = "<true/>";
 					} else {
 						value = "<false/>";
 					}
 					break;
-				case PluginConfigIOS::PlistItemType::RAW: {
-					String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String());
+				case PluginConfigAppleEmbedded::PlistItemType::RAW: {
+					String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String());
 					value = raw_value;
 				} break;
-				case PluginConfigIOS::PlistItemType::STRING_INPUT: {
-					String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, key, String());
+				case PluginConfigAppleEmbedded::PlistItemType::STRING_INPUT: {
+					String raw_value = config_file->get_value(PluginConfigAppleEmbedded::PLIST_SECTION, key, String());
 					value = raw_value;
 				} break;
 				default:
 					continue;
 			}
 
-			plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value };
+			plugin_config.plist[key_value] = PluginConfigAppleEmbedded::PlistItem{ key_type, value };
 		}
 	}
 

+ 6 - 6
platform/ios/export/godot_plugin_config.h → editor/export/plugin_config_apple_embedded.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  godot_plugin_config.h                                                 */
+/*  plugin_config_apple_embedded.h                                        */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -50,7 +50,7 @@ The `plist` section are optional.
 - **key**: key and value that would be added in Info.plist file.
  */
 
-struct PluginConfigIOS {
+struct PluginConfigAppleEmbedded {
 	inline static const char *PLUGIN_CONFIG_EXT = ".gdip";
 
 	inline static const char *CONFIG_SECTION = "config";
@@ -121,11 +121,11 @@ struct PluginConfigIOS {
 
 	static Vector<String> resolve_system_dependencies(Vector<String> p_paths);
 
-	static bool validate_plugin(PluginConfigIOS &plugin_config);
+	static bool validate_plugin(PluginConfigAppleEmbedded &plugin_config);
 
-	static String get_plugin_main_binary(PluginConfigIOS &plugin_config, bool p_debug);
+	static String get_plugin_main_binary(PluginConfigAppleEmbedded &plugin_config, bool p_debug);
 
-	static uint64_t get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path);
+	static uint64_t get_plugin_modification_time(const PluginConfigAppleEmbedded &plugin_config, const String &config_path);
 
-	static PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, const String &path);
+	static PluginConfigAppleEmbedded load_plugin_config(Ref<ConfigFile> config_file, const String &path);
 };

+ 5 - 5
editor/plugins/gdextension_export_plugin.h

@@ -115,9 +115,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
 			libs_added.insert(library_path);
 			add_shared_object(library_path, tags);
 
-			if (p_features.has("ios") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) {
+			if (p_features.has("apple_embedded") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) {
 				String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n"
-										 "extern void add_ios_init_callback(void (*cb)());\n"
+										 "extern void add_apple_embedded_platform_init_callback(void (*cb)());\n"
 										 "\n"
 										 "extern \"C\" void $ENTRY();\n"
 										 "void $ENTRY_init() {\n"
@@ -125,15 +125,15 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
 										 "}\n"
 										 "struct $ENTRY_struct {\n"
 										 "  $ENTRY_struct() {\n"
-										 "    add_ios_init_callback($ENTRY_init);\n"
+										 "    add_apple_embedded_platform_init_callback($ENTRY_init);\n"
 										 "  }\n"
 										 "};\n"
 										 "$ENTRY_struct $ENTRY_struct_instance;\n\n";
 				additional_code = additional_code.replace("$ENTRY", entry_symbol);
-				add_ios_cpp_code(additional_code);
+				add_apple_embedded_platform_cpp_code(additional_code);
 
 				String linker_flags = "-Wl,-U,_" + entry_symbol;
-				add_ios_linker_flags(linker_flags);
+				add_apple_embedded_platform_linker_flags(linker_flags);
 			}
 
 			// Update found library info.

+ 1 - 1
editor/plugins/lightmap_gi_editor_plugin.cpp

@@ -195,7 +195,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin() {
 #else
 	// Disable lightmap baking if the module is disabled at compile-time.
 	bake->set_disabled(true);
-#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+#if defined(ANDROID_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 	bake->set_tooltip_text(vformat(TTR("Lightmaps cannot be baked on %s."), OS::get_singleton()->get_name()));
 #else
 	bake->set_tooltip_text(TTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time."));

+ 2 - 0
editor/register_editor_types.cpp

@@ -47,6 +47,7 @@
 #include "editor/editor_undo_redo_manager.h"
 #include "editor/editor_vcs_interface.h"
 #include "editor/export/editor_export_platform.h"
+#include "editor/export/editor_export_platform_apple_embedded.h"
 #include "editor/export/editor_export_platform_extension.h"
 #include "editor/export/editor_export_platform_pc.h"
 #include "editor/export/editor_export_plugin.h"
@@ -163,6 +164,7 @@ void register_editor_types() {
 	GDREGISTER_CLASS(EditorExportPlugin);
 	GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
 	GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC);
+	GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformAppleEmbedded);
 	GDREGISTER_CLASS(EditorExportPlatformExtension);
 	GDREGISTER_ABSTRACT_CLASS(EditorExportPreset);
 

+ 1 - 1
main/main.cpp

@@ -3797,7 +3797,7 @@ static MainTimerSync main_timer_sync;
 int Main::start() {
 	OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Start");
 
-	ERR_FAIL_COND_V(!_start_success, false);
+	ERR_FAIL_COND_V(!_start_success, EXIT_FAILURE);
 
 	bool has_icon = false;
 	String positional_arg;

+ 17 - 1
methods.py

@@ -609,17 +609,33 @@ def Run(env, function):
     return Action(function, "$GENCOMSTR")
 
 
+def detect_darwin_toolchain_path(env):
+    var_name = "APPLE_TOOLCHAIN_PATH"
+    if not env[var_name]:
+        try:
+            xcode_path = subprocess.check_output(["xcode-select", "-p"]).strip().decode("utf-8")
+            if xcode_path:
+                env[var_name] = xcode_path + "/Toolchains/XcodeDefault.xctoolchain"
+        except (subprocess.CalledProcessError, OSError):
+            print_error("Failed to find SDK path while running 'xcode-select -p'.")
+            raise
+
+
 def detect_darwin_sdk_path(platform, env):
     sdk_name = ""
+
     if platform == "macos":
         sdk_name = "macosx"
         var_name = "MACOS_SDK_PATH"
+
     elif platform == "ios":
         sdk_name = "iphoneos"
         var_name = "IOS_SDK_PATH"
+
     elif platform == "iossimulator":
         sdk_name = "iphonesimulator"
         var_name = "IOS_SDK_PATH"
+
     else:
         raise Exception("Invalid platform argument passed to detect_darwin_sdk_path")
 
@@ -629,7 +645,7 @@ def detect_darwin_sdk_path(platform, env):
             if sdk_path:
                 env[var_name] = sdk_path
         except (subprocess.CalledProcessError, OSError):
-            print_error("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
+            print_error("Failed to find SDK path while running 'xcrun --sdk {} --show-sdk-path'.".format(sdk_name))
             raise
 
 

+ 0 - 4
misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj

@@ -7,7 +7,6 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-        054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 054F8BE52D38852F00B81423 /* MetalFX.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
 		1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; };
 		DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; };
 		$modules_buildfile
@@ -43,7 +42,6 @@
 		1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = "<group>"; };
 		1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = "<group>"; };
 		9039D3BD24C093AC0020482C /* MoltenVK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MoltenVK; path = MoltenVK.xcframework; sourceTree = "<group>"; };
-		054F8BE52D38852F00B81423 /* MetalFX.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalFX.framework; path = System/Library/Frameworks/MetalFX.framework; sourceTree = SDKROOT; };
 		D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
 		D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = "<group>"; };
@@ -62,7 +60,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				9039D3BE24C093AC0020482C /* MoltenVK.xcframework in Frameworks */,
-				054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */,
 				DEADBEEF2F582BE20003B888 /* $binary.xcframework */,
 				$modules_buildphase
 				$additional_pbx_frameworks_build
@@ -97,7 +94,6 @@
 			isa = PBXGroup;
 			children = (
 				9039D3BD24C093AC0020482C /* MoltenVK.xcframework */,
-				054F8BE52D38852F00B81423 /* MetalFX.framework */,
 				DEADBEEF1F582BE20003B888 /* $binary.xcframework */,
 				$modules_buildgrp
 				$additional_pbx_frameworks_refs

+ 2 - 2
modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs

@@ -406,7 +406,7 @@ namespace GodotTools.Export
                                 {
                                     if (platform == OS.Platforms.iOS && path.EndsWith(".dat", StringComparison.OrdinalIgnoreCase))
                                     {
-                                        AddIosBundleFile(path);
+                                        AddAppleEmbeddedPlatformBundleFile(path);
                                     }
                                     else
                                     {
@@ -453,7 +453,7 @@ namespace GodotTools.Export
                     throw new InvalidOperationException("Failed to generate xcframework.");
                 }
 
-                AddIosEmbeddedFramework(xcFrameworkPath);
+                AddAppleEmbeddedPlatformEmbeddedFramework(xcFrameworkPath);
             }
         }
 

+ 1 - 1
modules/mono/editor/hostfxr_resolver.cpp

@@ -82,7 +82,7 @@ namespace {
 String get_hostfxr_file_name() {
 #if defined(WINDOWS_ENABLED)
 	return "hostfxr.dll";
-#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#elif defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 	return "libhostfxr.dylib";
 #else
 	return "libhostfxr.so";

+ 3 - 3
modules/mono/mono_gd/gd_mono.cpp

@@ -450,7 +450,7 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
 
 #if defined(WINDOWS_ENABLED)
 	String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll");
-#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#elif defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 	String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib");
 #elif defined(ANDROID_ENABLED)
 	String native_aot_so_path = "lib" + assembly_name + ".so";
@@ -599,7 +599,7 @@ void GDMono::initialize() {
 
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
 
-#if !defined(IOS_ENABLED)
+#if !defined(APPLE_EMBEDDED_ENABLED)
 	// Check that the .NET assemblies directory exists before trying to use it.
 	if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
 		OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
@@ -639,7 +639,7 @@ void GDMono::initialize() {
 
 	void *godot_dll_handle = nullptr;
 
-#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED)
+#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(APPLE_EMBEDDED_ENABLED)
 	// Managed code can access it on its own on other platforms
 	godot_dll_handle = dlopen(nullptr, RTLD_NOW);
 #endif

+ 1 - 1
platform/android/export/export_plugin.cpp

@@ -713,7 +713,7 @@ Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_plugins() {
 		Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
 
 		if (!plugins_filenames.is_empty()) {
-			Ref<ConfigFile> config_file = memnew(ConfigFile);
+			Ref<ConfigFile> config_file;
 			for (int i = 0; i < plugins_filenames.size(); i++) {
 				PluginConfigAndroid config = PluginConfigAndroid::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i]));
 				if (config.valid_config) {

+ 4 - 0
platform/ios/README.md

@@ -3,6 +3,10 @@
 This folder contains the C++, Objective-C and Objective-C++ code for the iOS
 platform port.
 
+This platform derives from the Apple embedded abstract platform ([`drivers/apple_embedded`](/drivers/apple_embedded)).
+
+This platform uses shared Apple code ([`drivers/apple`](/drivers/apple)).
+
 See also [`misc/dist/ios_xcode`](/misc/dist/ios_xcode) folder for the Xcode
 project template used for packaging the iOS export templates.
 

+ 10 - 22
platform/ios/SCsub

@@ -1,28 +1,19 @@
 #!/usr/bin/env python
 from misc.utility.scons_hints import *
 
-import platform_ios_builders
+from platform_ios_builders import generate_bundle
+
+from platform_methods import combine_libs_apple_embedded
 
 Import("env")
 
 ios_lib = [
-    "godot_ios.mm",
-    "os_ios.mm",
-    "main.m",
-    "app_delegate.mm",
-    "view_controller.mm",
-    "ios.mm",
-    "rendering_context_driver_vulkan_ios.mm",
+    "device_metrics.mm",
+    "display_layer_ios.mm",
     "display_server_ios.mm",
-    "godot_view.mm",
-    "tts_ios.mm",
-    "display_layer.mm",
-    "godot_app_delegate.m",
-    "godot_view_renderer.mm",
-    "device_metrics.m",
-    "keyboard_input_view.mm",
-    "key_mapping_ios.mm",
-    "ios_terminal_logger.mm",
+    "godot_view_ios.mm",
+    "main_ios.mm",
+    "os_ios.mm",
 ]
 
 env_ios = env.Clone()
@@ -31,12 +22,9 @@ ios_lib = env_ios.add_library("ios", ios_lib)
 # (iOS) Enable module support
 env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
 
-
 combine_command = env_ios.CommandNoCache(
-    "#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], env.Run(platform_ios_builders.combine_libs)
+    "#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], env.Run(combine_libs_apple_embedded)
 )
 
 if env["generate_bundle"]:
-    env.AlwaysBuild(
-        env.CommandNoCache("generate_bundle", combine_command, env.Run(platform_ios_builders.generate_bundle))
-    )
+    env.AlwaysBuild(env.CommandNoCache("generate_bundle", combine_command, env.Run(generate_bundle)))

+ 2 - 2
platform/ios/api/api.cpp

@@ -33,11 +33,11 @@
 #if defined(IOS_ENABLED)
 
 void register_ios_api() {
-	godot_ios_plugins_initialize();
+	godot_apple_embedded_plugins_initialize();
 }
 
 void unregister_ios_api() {
-	godot_ios_plugins_deinitialize();
+	godot_apple_embedded_plugins_deinitialize();
 }
 
 #else

+ 2 - 2
platform/ios/api/api.h

@@ -31,8 +31,8 @@
 #pragma once
 
 #if defined(IOS_ENABLED)
-extern void godot_ios_plugins_initialize();
-extern void godot_ios_plugins_deinitialize();
+extern void godot_apple_embedded_plugins_initialize();
+extern void godot_apple_embedded_plugins_deinitialize();
 #endif
 
 void register_ios_api();

+ 16 - 18
platform/ios/detect.py

@@ -2,7 +2,7 @@ import os
 import sys
 from typing import TYPE_CHECKING
 
-from methods import detect_darwin_sdk_path, print_error, print_warning
+from methods import detect_darwin_sdk_path, detect_darwin_toolchain_path, print_error, print_warning
 from platform_methods import validate_arch
 
 if TYPE_CHECKING:
@@ -25,14 +25,11 @@ def get_opts():
 
     return [
         ("vulkan_sdk_path", "Path to the Vulkan SDK", ""),
-        (
-            "IOS_TOOLCHAIN_PATH",
-            "Path to iOS toolchain",
-            "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
-        ),
+        # APPLE_TOOLCHAIN_PATH Example: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain
+        (("APPLE_TOOLCHAIN_PATH", "IOS_TOOLCHAIN_PATH"), "Path to the Apple toolchain", ""),
         ("IOS_SDK_PATH", "Path to the iOS SDK", ""),
-        BoolVariable("ios_simulator", "Build for iOS Simulator", False),
-        ("ios_triple", "Triple for ios toolchain", ""),
+        (("apple_target_triple", "ios_triple"), "Triple for the corresponding target Apple platform toolchain", ""),
+        BoolVariable(("simulator", "ios_simulator"), "Build for Simulator", False),
         BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
     ]
 
@@ -62,6 +59,7 @@ def configure(env: "SConsEnvironment"):
     # Validate arch.
     supported_arches = ["x86_64", "arm64"]
     validate_arch(env["arch"], get_name(), supported_arches)
+    detect_darwin_toolchain_path(env)
 
     ## LTO
 
@@ -82,9 +80,9 @@ def configure(env: "SConsEnvironment"):
     if "OSXCROSS_IOS" in os.environ:
         env["osxcross"] = True
 
-    env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
+    env["ENV"]["PATH"] = env["APPLE_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
 
-    compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
+    compiler_path = "$APPLE_TOOLCHAIN_PATH/usr/bin/${apple_target_triple}"
 
     ccache_path = os.environ.get("CCACHE")
     if ccache_path is None:
@@ -102,7 +100,7 @@ def configure(env: "SConsEnvironment"):
 
     ## Compile flags
 
-    if env["ios_simulator"]:
+    if env["simulator"]:
         detect_darwin_sdk_path("iossimulator", env)
         env.Append(ASFLAGS=["-mios-simulator-version-min=12.0"])
         env.Append(CCFLAGS=["-mios-simulator-version-min=12.0"])
@@ -114,8 +112,8 @@ def configure(env: "SConsEnvironment"):
         env.Append(CCFLAGS=["-miphoneos-version-min=12.0"])
 
     if env["arch"] == "x86_64":
-        if not env["ios_simulator"]:
-            print_error("Building for iOS with 'arch=x86_64' requires 'ios_simulator=yes'.")
+        if not env["simulator"]:
+            print_error("Building for iOS with 'arch=x86_64' requires 'simulator=yes'.")
             sys.exit(255)
 
         env["ENV"]["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
@@ -149,10 +147,10 @@ def configure(env: "SConsEnvironment"):
     )
 
     env.Prepend(CPPPATH=["#platform/ios"])
-    env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
+    env.Append(CPPDEFINES=["IOS_ENABLED", "APPLE_EMBEDDED_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
 
-    if env["metal"] and env["ios_simulator"]:
-        print_warning("iOS simulator does not support the Metal rendering driver")
+    if env["metal"] and env["simulator"]:
+        print_warning("iOS Simulator does not support the Metal rendering driver")
         env["metal"] = False
 
     if env["metal"]:
@@ -166,8 +164,8 @@ def configure(env: "SConsEnvironment"):
         )
         env.Prepend(CPPEXTPATH=["#thirdparty/spirv-cross"])
 
-    if env["vulkan"] and env["ios_simulator"]:
-        print_warning("iOS simulator does not support the Vulkan rendering driver")
+    if env["vulkan"] and env["simulator"]:
+        print_warning("iOS Simulator does not support the Vulkan rendering driver")
         env["vulkan"] = False
 
     if env["vulkan"]:

+ 1 - 1
platform/ios/device_metrics.h

@@ -32,7 +32,7 @@
 
 #import <Foundation/Foundation.h>
 
-@interface GodotDeviceMetrics : NSObject
+@interface GDTDeviceMetrics : NSObject
 
 @property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
 

+ 2 - 2
platform/ios/device_metrics.m → platform/ios/device_metrics.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  device_metrics.m                                                      */
+/*  device_metrics.mm                                                     */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -30,7 +30,7 @@
 
 #import "device_metrics.h"
 
-@implementation GodotDeviceMetrics
+@implementation GDTDeviceMetrics
 
 + (NSDictionary *)dpiList {
 	return @{

+ 7 - 14
platform/ios/display_layer.h → platform/ios/display_layer_ios.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  display_layer.h                                                       */
+/*  display_layer_ios.h                                                   */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -30,32 +30,25 @@
 
 #pragma once
 
+#include "drivers/apple_embedded/display_layer_apple_embedded.h"
+
 #import <OpenGLES/EAGLDrawable.h>
 #import <QuartzCore/QuartzCore.h>
 
-@protocol DisplayLayer <NSObject>
-
-- (void)startRenderDisplayLayer;
-- (void)stopRenderDisplayLayer;
-- (void)initializeDisplayLayer;
-- (void)layoutDisplayLayer;
-
-@end
-
 // An ugly workaround for iOS simulator
 #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
 #if defined(__IPHONE_13_0)
 API_AVAILABLE(ios(13.0))
-@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+@interface GDTMetalLayer : CAMetalLayer <GDTDisplayLayer>
 #else
-@interface GodotMetalLayer : CALayer <DisplayLayer>
+@interface GDTMetalLayer : CALayer <GDTDisplayLayer>
 #endif
 #else
-@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+@interface GDTMetalLayer : CAMetalLayer <GDTDisplayLayer>
 #endif
 @end
 
 API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
-@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+@interface GDTOpenGLLayer : CAEAGLLayer <GDTDisplayLayer>
 
 @end

+ 4 - 4
platform/ios/display_layer.mm → platform/ios/display_layer_ios.mm

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  display_layer.mm                                                      */
+/*  display_layer_ios.mm                                                  */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,7 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#import "display_layer.h"
+#import "display_layer_ios.h"
 
 #import "display_server_ios.h"
 #import "os_ios.h"
@@ -46,7 +46,7 @@
 #import <QuartzCore/QuartzCore.h>
 #import <UIKit/UIKit.h>
 
-@implementation GodotMetalLayer
+@implementation GDTMetalLayer
 
 - (void)initializeDisplayLayer {
 #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
@@ -69,7 +69,7 @@
 
 @end
 
-@implementation GodotOpenGLLayer {
+@implementation GDTOpenGLLayer {
 	// The pixel dimensions of the backbuffer
 	GLint backingWidth;
 	GLint backingHeight;

+ 3 - 187
platform/ios/display_server_ios.h

@@ -30,209 +30,25 @@
 
 #pragma once
 
-#include "core/input/input.h"
-#include "servers/display_server.h"
+#include "drivers/apple_embedded/display_server_apple_embedded.h"
 
-#if defined(RD_ENABLED)
-#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
-#include "servers/rendering/rendering_device.h"
-
-#if defined(VULKAN_ENABLED)
-#import "rendering_context_driver_vulkan_ios.h"
-
-#include "drivers/vulkan/godot_vulkan.h"
-#endif // VULKAN_ENABLED
-
-#if defined(METAL_ENABLED)
-#import "drivers/metal/rendering_context_driver_metal.h"
-#endif // METAL_ENABLED
-#endif // RD_ENABLED
-
-#if defined(GLES3_ENABLED)
-#include "drivers/gles3/rasterizer_gles3.h"
-#endif // GLES3_ENABLED
-
-#import <Foundation/Foundation.h>
-#import <QuartzCore/CAMetalLayer.h>
-
-class DisplayServerIOS : public DisplayServer {
-	GDSOFTCLASS(DisplayServerIOS, DisplayServer);
+class DisplayServerIOS : public DisplayServerAppleEmbedded {
+	GDSOFTCLASS(DisplayServerIOS, DisplayServerAppleEmbedded);
 
 	_THREAD_SAFE_CLASS_
 
-#if defined(RD_ENABLED)
-	RenderingContextDriver *rendering_context = nullptr;
-	RenderingDevice *rendering_device = nullptr;
-#endif
-	NativeMenu *native_menu = nullptr;
-
-	id tts = nullptr;
-
-	DisplayServer::ScreenOrientation screen_orientation;
-
-	ObjectID window_attached_instance_id;
-
-	Callable window_event_callback;
-	Callable window_resize_callback;
-	Callable input_event_callback;
-	Callable input_text_callback;
-
-	Callable system_theme_changed;
-
-	int virtual_keyboard_height = 0;
-
-	void perform_event(const Ref<InputEvent> &p_event);
-
-	void initialize_tts() const;
-
 	DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
 	~DisplayServerIOS();
 
 public:
-	String rendering_driver;
-
 	static DisplayServerIOS *get_singleton();
 
 	static void register_ios_driver();
 	static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
-	static Vector<String> get_rendering_drivers_func();
-
-	// MARK: - Events
-
-	virtual void process_events() override;
-
-	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
-
-	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
-	void send_input_event(const Ref<InputEvent> &p_event) const;
-	void send_input_text(const String &p_text) const;
-	void send_window_event(DisplayServer::WindowEvent p_event) const;
-	void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
-
-	void emit_system_theme_changed();
-
-	// MARK: - Input
-
-	// MARK: Touches and Apple Pencil
 
-	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click);
-	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt);
-	void touches_canceled(int p_idx);
-
-	// MARK: Keyboard
-
-	void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
-	bool is_keyboard_active() const;
-
-	// MARK: Motion
-
-	void update_gravity(const Vector3 &p_gravity);
-	void update_accelerometer(const Vector3 &p_accelerometer);
-	void update_magnetometer(const Vector3 &p_magnetometer);
-	void update_gyroscope(const Vector3 &p_gyroscope);
-
-	// MARK: -
-
-	virtual bool has_feature(Feature p_feature) const override;
 	virtual String get_name() const override;
 
-	virtual bool tts_is_speaking() const override;
-	virtual bool tts_is_paused() const override;
-	virtual TypedArray<Dictionary> tts_get_voices() const override;
-
-	virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
-	virtual void tts_pause() override;
-	virtual void tts_resume() override;
-	virtual void tts_stop() override;
-
-	virtual bool is_dark_mode_supported() const override;
-	virtual bool is_dark_mode() const override;
-	virtual void set_system_theme_change_callback(const Callable &p_callable) override;
-
-	virtual Rect2i get_display_safe_area() const override;
-
-	virtual int get_screen_count() const override;
-	virtual int get_primary_screen() const override;
-	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
-	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
-	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
 	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
 	virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
 	virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
-
-	virtual Vector<DisplayServer::WindowID> get_window_list() const override;
-
-	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
-
-	virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
-
-	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
-	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
-
-	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
-	virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
-	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
-
-	virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
-
-	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
-	virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual float screen_get_max_scale() const override;
-
-	virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
-	virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
-
-	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
-
-	virtual bool can_any_window_draw() const override;
-
-	virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
-	virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
-
-	virtual bool is_touchscreen_available() const override;
-
-	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override;
-	virtual void virtual_keyboard_hide() override;
-
-	void virtual_keyboard_set_height(int height);
-	virtual int virtual_keyboard_get_height() const override;
-	virtual bool has_hardware_keyboard() const override;
-
-	virtual void clipboard_set(const String &p_text) override;
-	virtual String clipboard_get() const override;
-
-	virtual void screen_set_keep_on(bool p_enable) override;
-	virtual bool screen_is_kept_on() const override;
-
-	void resize_window(CGSize size);
-	virtual void swap_buffers() override {}
 };

+ 5 - 744
platform/ios/display_server_ios.mm

@@ -30,492 +30,33 @@
 
 #import "display_server_ios.h"
 
-#import "app_delegate.h"
 #import "device_metrics.h"
-#import "godot_view.h"
-#import "ios.h"
-#import "key_mapping_ios.h"
-#import "keyboard_input_view.h"
-#import "os_ios.h"
-#import "tts_ios.h"
-#import "view_controller.h"
-
-#include "core/config/project_settings.h"
-#include "core/io/file_access_pack.h"
 
+#import <UIKit/UIKit.h>
 #import <sys/utsname.h>
 
-#import <GameController/GameController.h>
-
-static const float kDisplayServerIOSAcceleration = 1.f;
-
 DisplayServerIOS *DisplayServerIOS::get_singleton() {
-	return (DisplayServerIOS *)DisplayServer::get_singleton();
+	return (DisplayServerIOS *)DisplayServerAppleEmbedded::get_singleton();
 }
 
-DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
-	KeyMappingIOS::initialize();
-
-	rendering_driver = p_rendering_driver;
-
-	// Init TTS
-	bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
-	if (tts_enabled) {
-		initialize_tts();
-	}
-	native_menu = memnew(NativeMenu);
-
-#if defined(RD_ENABLED)
-	rendering_context = nullptr;
-	rendering_device = nullptr;
-
-	CALayer *layer = nullptr;
-
-	union {
-#ifdef VULKAN_ENABLED
-		RenderingContextDriverVulkanIOS::WindowPlatformData vulkan;
-#endif
-#ifdef METAL_ENABLED
-		GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wunguarded-availability")
-		// Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
-		RenderingContextDriverMetal::WindowPlatformData metal;
-		GODOT_CLANG_WARNING_POP
-#endif
-	} wpd;
-
-#if defined(VULKAN_ENABLED)
-	if (rendering_driver == "vulkan") {
-		layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
-		if (!layer) {
-			ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer.");
-		}
-		wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
-		rendering_context = memnew(RenderingContextDriverVulkanIOS);
-	}
-#endif
-#ifdef METAL_ENABLED
-	if (rendering_driver == "metal") {
-		if (@available(iOS 14.0, *)) {
-			layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"metal"];
-			wpd.metal.layer = (CAMetalLayer *)layer;
-			rendering_context = memnew(RenderingContextDriverMetal);
-		} else {
-			OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later.");
-			r_error = ERR_UNAVAILABLE;
-			return;
-		}
-	}
-#endif
-	if (rendering_context) {
-		if (rendering_context->initialize() != OK) {
-			memdelete(rendering_context);
-			rendering_context = nullptr;
-#if defined(GLES3_ENABLED)
-			bool fallback_to_opengl3 = GLOBAL_GET("rendering/rendering_device/fallback_to_opengl3");
-			if (fallback_to_opengl3 && rendering_driver != "opengl3") {
-				WARN_PRINT("Your device seem not to support MoltenVK or Metal, switching to OpenGL 3.");
-				rendering_driver = "opengl3";
-				OS::get_singleton()->set_current_rendering_method("gl_compatibility");
-				OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
-			} else
-#endif
-			{
-				ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
-				r_error = ERR_UNAVAILABLE;
-				return;
-			}
-		}
-	}
-
-	if (rendering_context) {
-		if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) {
-			ERR_PRINT(vformat("Failed to create %s window.", rendering_driver));
-			memdelete(rendering_context);
-			rendering_context = nullptr;
-			r_error = ERR_UNAVAILABLE;
-			return;
-		}
-
-		Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
-		rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height);
-		rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
-
-		rendering_device = memnew(RenderingDevice);
-		if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
-			rendering_device = nullptr;
-			memdelete(rendering_context);
-			rendering_context = nullptr;
-			r_error = ERR_UNAVAILABLE;
-			return;
-		}
-		rendering_device->screen_create(MAIN_WINDOW_ID);
-
-		RendererCompositorRD::make_current();
-	}
-#endif
-
-#if defined(GLES3_ENABLED)
-	if (rendering_driver == "opengl3") {
-		CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"opengl3"];
-
-		if (!layer) {
-			ERR_FAIL_MSG("Failed to create iOS OpenGLES rendering layer.");
-		}
-
-		RasterizerGLES3::make_current(false);
-	}
-#endif
-
-	bool keep_screen_on = bool(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
-	screen_set_keep_on(keep_screen_on);
-
-	Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
-
-	r_error = OK;
+DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) :
+		DisplayServerAppleEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error) {
 }
 
 DisplayServerIOS::~DisplayServerIOS() {
-	if (native_menu) {
-		memdelete(native_menu);
-		native_menu = nullptr;
-	}
-
-#if defined(RD_ENABLED)
-	if (rendering_device) {
-		rendering_device->screen_free(MAIN_WINDOW_ID);
-		memdelete(rendering_device);
-		rendering_device = nullptr;
-	}
-
-	if (rendering_context) {
-		rendering_context->window_destroy(MAIN_WINDOW_ID);
-		memdelete(rendering_context);
-		rendering_context = nullptr;
-	}
-#endif
 }
 
 DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
 	return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error));
 }
 
-Vector<String> DisplayServerIOS::get_rendering_drivers_func() {
-	Vector<String> drivers;
-
-#if defined(VULKAN_ENABLED)
-	drivers.push_back("vulkan");
-#endif
-#if defined(METAL_ENABLED)
-	if (@available(ios 14.0, *)) {
-		drivers.push_back("metal");
-	}
-#endif
-#if defined(GLES3_ENABLED)
-	drivers.push_back("opengl3");
-#endif
-
-	return drivers;
-}
-
 void DisplayServerIOS::register_ios_driver() {
 	register_create_function("iOS", create_func, get_rendering_drivers_func);
 }
 
-// MARK: Events
-
-void DisplayServerIOS::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
-	window_resize_callback = p_callable;
-}
-
-void DisplayServerIOS::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
-	window_event_callback = p_callable;
-}
-void DisplayServerIOS::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
-	input_event_callback = p_callable;
-}
-
-void DisplayServerIOS::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
-	input_text_callback = p_callable;
-}
-
-void DisplayServerIOS::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-void DisplayServerIOS::process_events() {
-	Input::get_singleton()->flush_buffered_events();
-}
-
-void DisplayServerIOS::_dispatch_input_events(const Ref<InputEvent> &p_event) {
-	DisplayServerIOS::get_singleton()->send_input_event(p_event);
-}
-
-void DisplayServerIOS::send_input_event(const Ref<InputEvent> &p_event) const {
-	_window_callback(input_event_callback, p_event);
-}
-
-void DisplayServerIOS::send_input_text(const String &p_text) const {
-	_window_callback(input_text_callback, p_text);
-}
-
-void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) const {
-	_window_callback(window_event_callback, int(p_event));
-}
-
-void DisplayServerIOS::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
-	if (p_callable.is_valid()) {
-		p_callable.call(p_arg);
-	}
-}
-
-// MARK: - Input
-
-// MARK: Touches
-
-void DisplayServerIOS::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_double_click) {
-	Ref<InputEventScreenTouch> ev;
-	ev.instantiate();
-
-	ev->set_index(p_idx);
-	ev->set_pressed(p_pressed);
-	ev->set_position(Vector2(p_x, p_y));
-	ev->set_double_tap(p_double_click);
-	perform_event(ev);
-}
-
-void DisplayServerIOS::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y, float p_pressure, Vector2 p_tilt) {
-	Ref<InputEventScreenDrag> ev;
-	ev.instantiate();
-	ev->set_index(p_idx);
-	ev->set_pressure(p_pressure);
-	ev->set_tilt(p_tilt);
-	ev->set_position(Vector2(p_x, p_y));
-	ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
-	ev->set_relative_screen_position(ev->get_relative());
-	perform_event(ev);
-}
-
-void DisplayServerIOS::perform_event(const Ref<InputEvent> &p_event) {
-	Input::get_singleton()->parse_input_event(p_event);
-}
-
-void DisplayServerIOS::touches_canceled(int p_idx) {
-	touch_press(p_idx, -1, -1, false, false);
-}
-
-// MARK: Keyboard
-
-void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
-	Ref<InputEventKey> ev;
-	ev.instantiate();
-	ev->set_echo(false);
-	ev->set_pressed(p_pressed);
-	ev->set_keycode(fix_keycode(p_char, p_key));
-	if (@available(iOS 13.4, *)) {
-		if (p_key != Key::SHIFT) {
-			ev->set_shift_pressed(p_modifier & UIKeyModifierShift);
-		}
-		if (p_key != Key::CTRL) {
-			ev->set_ctrl_pressed(p_modifier & UIKeyModifierControl);
-		}
-		if (p_key != Key::ALT) {
-			ev->set_alt_pressed(p_modifier & UIKeyModifierAlternate);
-		}
-		if (p_key != Key::META) {
-			ev->set_meta_pressed(p_modifier & UIKeyModifierCommand);
-		}
-	}
-	ev->set_key_label(p_unshifted);
-	ev->set_physical_keycode(p_physical);
-	ev->set_unicode(fix_unicode(p_char));
-	ev->set_location(p_location);
-	perform_event(ev);
-}
-
-// MARK: Motion
-
-void DisplayServerIOS::update_gravity(const Vector3 &p_gravity) {
-	Input::get_singleton()->set_gravity(p_gravity);
-}
-
-void DisplayServerIOS::update_accelerometer(const Vector3 &p_accelerometer) {
-	Input::get_singleton()->set_accelerometer(p_accelerometer / kDisplayServerIOSAcceleration);
-}
-
-void DisplayServerIOS::update_magnetometer(const Vector3 &p_magnetometer) {
-	Input::get_singleton()->set_magnetometer(p_magnetometer);
-}
-
-void DisplayServerIOS::update_gyroscope(const Vector3 &p_gyroscope) {
-	Input::get_singleton()->set_gyroscope(p_gyroscope);
-}
-
-// MARK: -
-
-bool DisplayServerIOS::has_feature(Feature p_feature) const {
-	switch (p_feature) {
-#ifndef DISABLE_DEPRECATED
-		case FEATURE_GLOBAL_MENU: {
-			return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));
-		} break;
-#endif
-		// case FEATURE_CURSOR_SHAPE:
-		// case FEATURE_CUSTOM_CURSOR_SHAPE:
-		// case FEATURE_HIDPI:
-		// case FEATURE_ICON:
-		// case FEATURE_IME:
-		// case FEATURE_MOUSE:
-		// case FEATURE_MOUSE_WARP:
-		// case FEATURE_NATIVE_DIALOG:
-		// case FEATURE_NATIVE_DIALOG_INPUT:
-		// case FEATURE_NATIVE_DIALOG_FILE:
-		// case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
-		// case FEATURE_NATIVE_DIALOG_FILE_MIME:
-		// case FEATURE_NATIVE_ICON:
-		// case FEATURE_WINDOW_TRANSPARENCY:
-		case FEATURE_CLIPBOARD:
-		case FEATURE_KEEP_SCREEN_ON:
-		case FEATURE_ORIENTATION:
-		case FEATURE_TOUCHSCREEN:
-		case FEATURE_VIRTUAL_KEYBOARD:
-		case FEATURE_TEXT_TO_SPEECH:
-			return true;
-		default:
-			return false;
-	}
-}
-
 String DisplayServerIOS::get_name() const {
 	return "iOS";
 }
-void DisplayServerIOS::initialize_tts() const {
-	const_cast<DisplayServerIOS *>(this)->tts = [[TTS_IOS alloc] init];
-}
-
-bool DisplayServerIOS::tts_is_speaking() const {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL_V(tts, false);
-	return [tts isSpeaking];
-}
-
-bool DisplayServerIOS::tts_is_paused() const {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL_V(tts, false);
-	return [tts isPaused];
-}
-
-TypedArray<Dictionary> DisplayServerIOS::tts_get_voices() const {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
-	return [tts getVoices];
-}
-
-void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL(tts);
-	[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
-}
-
-void DisplayServerIOS::tts_pause() {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL(tts);
-	[tts pauseSpeaking];
-}
-
-void DisplayServerIOS::tts_resume() {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL(tts);
-	[tts resumeSpeaking];
-}
-
-void DisplayServerIOS::tts_stop() {
-	if (unlikely(!tts)) {
-		initialize_tts();
-	}
-	ERR_FAIL_NULL(tts);
-	[tts stopSpeaking];
-}
-
-bool DisplayServerIOS::is_dark_mode_supported() const {
-	if (@available(iOS 13.0, *)) {
-		return true;
-	} else {
-		return false;
-	}
-}
-
-bool DisplayServerIOS::is_dark_mode() const {
-	if (@available(iOS 13.0, *)) {
-		return [UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark;
-	} else {
-		return false;
-	}
-}
-
-void DisplayServerIOS::set_system_theme_change_callback(const Callable &p_callable) {
-	system_theme_changed = p_callable;
-}
-
-void DisplayServerIOS::emit_system_theme_changed() {
-	if (system_theme_changed.is_valid()) {
-		Variant ret;
-		Callable::CallError ce;
-		system_theme_changed.callp(nullptr, 0, ret, ce);
-		if (ce.error != Callable::CallError::CALL_OK) {
-			ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));
-		}
-	}
-}
-
-Rect2i DisplayServerIOS::get_display_safe_area() const {
-	UIEdgeInsets insets = UIEdgeInsetsZero;
-	UIView *view = AppDelegate.viewController.godotView;
-	if ([view respondsToSelector:@selector(safeAreaInsets)]) {
-		insets = [view safeAreaInsets];
-	}
-	float scale = screen_get_scale();
-	Size2i insets_position = Size2i(insets.left, insets.top) * scale;
-	Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
-	return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
-}
-
-int DisplayServerIOS::get_screen_count() const {
-	return 1;
-}
-
-int DisplayServerIOS::get_primary_screen() const {
-	return 0;
-}
-
-Point2i DisplayServerIOS::screen_get_position(int p_screen) const {
-	return Size2i();
-}
-
-Size2i DisplayServerIOS::screen_get_size(int p_screen) const {
-	CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
-
-	if (!layer) {
-		return Size2i();
-	}
-
-	return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
-}
-
-Rect2i DisplayServerIOS::screen_get_usable_rect(int p_screen) const {
-	return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
-}
 
 int DisplayServerIOS::screen_get_dpi(int p_screen) const {
 	struct utsname systemInfo;
@@ -523,7 +64,7 @@ int DisplayServerIOS::screen_get_dpi(int p_screen) const {
 
 	NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
 
-	NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList];
+	NSDictionary *iOSModelToDPI = [GDTDeviceMetrics dpiList];
 
 	for (NSArray *keyArray in iOSModelToDPI) {
 		if ([keyArray containsObject:string]) {
@@ -565,283 +106,3 @@ float DisplayServerIOS::screen_get_refresh_rate(int p_screen) const {
 float DisplayServerIOS::screen_get_scale(int p_screen) const {
 	return [UIScreen mainScreen].scale;
 }
-
-Vector<DisplayServer::WindowID> DisplayServerIOS::get_window_list() const {
-	Vector<DisplayServer::WindowID> list;
-	list.push_back(MAIN_WINDOW_ID);
-	return list;
-}
-
-DisplayServer::WindowID DisplayServerIOS::get_window_at_screen_position(const Point2i &p_position) const {
-	return MAIN_WINDOW_ID;
-}
-
-int64_t DisplayServerIOS::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
-	ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0);
-	switch (p_handle_type) {
-		case DISPLAY_HANDLE: {
-			return 0; // Not supported.
-		}
-		case WINDOW_HANDLE: {
-			return (int64_t)AppDelegate.viewController;
-		}
-		case WINDOW_VIEW: {
-			return (int64_t)AppDelegate.viewController.godotView;
-		}
-		default: {
-			return 0;
-		}
-	}
-}
-
-void DisplayServerIOS::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
-	window_attached_instance_id = p_instance;
-}
-
-ObjectID DisplayServerIOS::window_get_attached_instance_id(WindowID p_window) const {
-	return window_attached_instance_id;
-}
-
-void DisplayServerIOS::window_set_title(const String &p_title, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-int DisplayServerIOS::window_get_current_screen(WindowID p_window) const {
-	return SCREEN_OF_MAIN_WINDOW;
-}
-
-void DisplayServerIOS::window_set_current_screen(int p_screen, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-Point2i DisplayServerIOS::window_get_position(WindowID p_window) const {
-	return Point2i();
-}
-
-Point2i DisplayServerIOS::window_get_position_with_decorations(WindowID p_window) const {
-	return Point2i();
-}
-
-void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) {
-	// Probably not supported for single window iOS app
-}
-
-void DisplayServerIOS::window_set_transient(WindowID p_window, WindowID p_parent) {
-	// Probably not supported for iOS
-}
-
-void DisplayServerIOS::window_set_max_size(const Size2i p_size, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-Size2i DisplayServerIOS::window_get_max_size(WindowID p_window) const {
-	return Size2i();
-}
-
-void DisplayServerIOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-Size2i DisplayServerIOS::window_get_min_size(WindowID p_window) const {
-	return Size2i();
-}
-
-void DisplayServerIOS::window_set_size(const Size2i p_size, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-Size2i DisplayServerIOS::window_get_size(WindowID p_window) const {
-	CGRect screenBounds = [UIScreen mainScreen].bounds;
-	return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
-}
-
-Size2i DisplayServerIOS::window_get_size_with_decorations(WindowID p_window) const {
-	return window_get_size(p_window);
-}
-
-void DisplayServerIOS::window_set_mode(WindowMode p_mode, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-DisplayServer::WindowMode DisplayServerIOS::window_get_mode(WindowID p_window) const {
-	return WindowMode::WINDOW_MODE_FULLSCREEN;
-}
-
-bool DisplayServerIOS::window_is_maximize_allowed(WindowID p_window) const {
-	return false;
-}
-
-void DisplayServerIOS::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-bool DisplayServerIOS::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
-	return false;
-}
-
-void DisplayServerIOS::window_request_attention(WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-void DisplayServerIOS::window_move_to_foreground(WindowID p_window) {
-	// Probably not supported for iOS
-}
-
-bool DisplayServerIOS::window_is_focused(WindowID p_window) const {
-	return true;
-}
-
-float DisplayServerIOS::screen_get_max_scale() const {
-	return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
-}
-
-void DisplayServerIOS::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
-	screen_orientation = p_orientation;
-	if (@available(iOS 16.0, *)) {
-		[AppDelegate.viewController setNeedsUpdateOfSupportedInterfaceOrientations];
-	} else {
-		[UIViewController attemptRotationToDeviceOrientation];
-	}
-}
-
-DisplayServer::ScreenOrientation DisplayServerIOS::screen_get_orientation(int p_screen) const {
-	return screen_orientation;
-}
-
-bool DisplayServerIOS::window_can_draw(WindowID p_window) const {
-	return true;
-}
-
-bool DisplayServerIOS::can_any_window_draw() const {
-	return true;
-}
-
-bool DisplayServerIOS::is_touchscreen_available() const {
-	return true;
-}
-
-_FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text, int p_pos) {
-	int limit = p_pos;
-	for (int i = 0; i < MIN(p_existing_text.length(), p_pos); i++) {
-		if (p_existing_text[i] > 0xffff) {
-			limit++;
-		}
-	}
-	return limit;
-}
-
-void DisplayServerIOS::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) {
-	NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
-
-	AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
-	AppDelegate.viewController.keyboardView.textContentType = nil;
-	switch (p_type) {
-		case KEYBOARD_TYPE_DEFAULT: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
-		} break;
-		case KEYBOARD_TYPE_MULTILINE: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
-		} break;
-		case KEYBOARD_TYPE_NUMBER: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeNumberPad;
-		} break;
-		case KEYBOARD_TYPE_NUMBER_DECIMAL: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDecimalPad;
-		} break;
-		case KEYBOARD_TYPE_PHONE: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypePhonePad;
-			AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeTelephoneNumber;
-		} break;
-		case KEYBOARD_TYPE_EMAIL_ADDRESS: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeEmailAddress;
-			AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeEmailAddress;
-		} break;
-		case KEYBOARD_TYPE_PASSWORD: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeDefault;
-			AppDelegate.viewController.keyboardView.textContentType = UITextContentTypePassword;
-		} break;
-		case KEYBOARD_TYPE_URL: {
-			AppDelegate.viewController.keyboardView.keyboardType = UIKeyboardTypeWebSearch;
-			AppDelegate.viewController.keyboardView.textContentType = UITextContentTypeURL;
-		} break;
-	}
-
-	[AppDelegate.viewController.keyboardView
-			becomeFirstResponderWithString:existingString
-							   cursorStart:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_start)
-								 cursorEnd:_convert_utf32_offset_to_utf16(p_existing_text, p_cursor_end)];
-}
-
-bool DisplayServerIOS::is_keyboard_active() const {
-	return [AppDelegate.viewController.keyboardView isFirstResponder];
-}
-
-void DisplayServerIOS::virtual_keyboard_hide() {
-	[AppDelegate.viewController.keyboardView resignFirstResponder];
-}
-
-void DisplayServerIOS::virtual_keyboard_set_height(int height) {
-	virtual_keyboard_height = height * screen_get_max_scale();
-}
-
-int DisplayServerIOS::virtual_keyboard_get_height() const {
-	return virtual_keyboard_height;
-}
-
-bool DisplayServerIOS::has_hardware_keyboard() const {
-	if (@available(iOS 14.0, *)) {
-		return [GCKeyboard coalescedKeyboard];
-	} else {
-		return false;
-	}
-}
-
-void DisplayServerIOS::clipboard_set(const String &p_text) {
-	[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()];
-}
-
-String DisplayServerIOS::clipboard_get() const {
-	NSString *text = [UIPasteboard generalPasteboard].string;
-
-	return String::utf8([text UTF8String]);
-}
-
-void DisplayServerIOS::screen_set_keep_on(bool p_enable) {
-	[UIApplication sharedApplication].idleTimerDisabled = p_enable;
-}
-
-bool DisplayServerIOS::screen_is_kept_on() const {
-	return [UIApplication sharedApplication].idleTimerDisabled;
-}
-
-void DisplayServerIOS::resize_window(CGSize viewSize) {
-	Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
-
-#if defined(RD_ENABLED)
-	if (rendering_context) {
-		rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y);
-	}
-#endif
-
-	Variant resize_rect = Rect2i(Point2i(), size);
-	_window_callback(window_resize_callback, resize_rect);
-}
-
-void DisplayServerIOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
-	_THREAD_SAFE_METHOD_
-#if defined(RD_ENABLED)
-	if (rendering_context) {
-		rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
-	}
-#endif
-}
-
-DisplayServer::VSyncMode DisplayServerIOS::window_get_vsync_mode(WindowID p_window) const {
-	_THREAD_SAFE_METHOD_
-#if defined(RD_ENABLED)
-	if (rendering_context) {
-		return rendering_context->window_get_vsync_mode(p_window);
-	}
-#endif
-	return DisplayServer::VSYNC_ENABLED;
-}

+ 10 - 10
platform/ios/doc_classes/EditorExportPlatformIOS.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<class name="EditorExportPlatformIOS" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="EditorExportPlatformIOS" inherits="EditorExportPlatformAppleEmbedded" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
 	<brief_description>
 		Exporter for iOS.
 	</brief_description>
@@ -24,10 +24,10 @@
 			Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]).
 		</member>
 		<member name="application/code_sign_identity_debug" type="String" setter="" getter="">
-			The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for debug export.
+			The "Full Name", "Common Name", or SHA-1 hash of the signing identity used for debug export.
 		</member>
 		<member name="application/code_sign_identity_release" type="String" setter="" getter="">
-			The "Full Name", "Common Name" or SHA-1 hash of the signing identity used for release export.
+			The "Full Name", "Common Name", or SHA-1 hash of the signing identity used for release export.
 		</member>
 		<member name="application/delete_old_export_files_unconditionally" type="bool" setter="" getter="">
 			If [code]true[/code], existing "project name" and "project name.xcodeproj" in the export destination directory will be unconditionally deleted during export.
@@ -48,20 +48,20 @@
 			Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]).
 		</member>
 		<member name="application/provisioning_profile_specifier_debug" type="String" setter="" getter="">
-			Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
-			Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_DEBUG[/code].
+			Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
+			Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG[/code].
 		</member>
 		<member name="application/provisioning_profile_specifier_release" type="String" setter="" getter="">
-			Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
-			Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_RELEASE[/code].
+			Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
+			Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE[/code].
 		</member>
 		<member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter="">
 			UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url].
-			Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG[/code].
+			Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_DEBUG[/code].
 		</member>
 		<member name="application/provisioning_profile_uuid_release" type="String" setter="" getter="">
 			UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url].
-			Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE[/code].
+			Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROVISIONING_PROFILE_UUID_RELEASE[/code].
 		</member>
 		<member name="application/short_version" type="String" setter="" getter="">
 			Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty.
@@ -86,7 +86,7 @@
 		</member>
 		<member name="capabilities/performance_a12" type="bool" setter="" getter="">
 			Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features).
-			Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer.
+			Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen), and newer.
 		</member>
 		<member name="capabilities/performance_gaming_tier" type="bool" setter="" getter="">
 			Requires the graphics performance and features of the A17 Pro and later chips.

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 97 - 848
platform/ios/export/export_plugin.cpp


+ 10 - 247
platform/ios/export/export_plugin.h

@@ -30,268 +30,31 @@
 
 #pragma once
 
-#include "godot_plugin_config.h"
+#include "editor/export/editor_export_platform_apple_embedded.h"
 
-#include "core/config/project_settings.h"
-#include "core/io/file_access.h"
-#include "core/io/image_loader.h"
-#include "core/io/marshalls.h"
-#include "core/io/resource_saver.h"
-#include "core/io/zip_io.h"
-#include "core/os/os.h"
-#include "core/templates/safe_refcount.h"
-#include "editor/editor_settings.h"
-#include "editor/export/editor_export_platform.h"
-#include "main/splash.gen.h"
-#include "scene/resources/image_texture.h"
+class EditorExportPlatformIOS : public EditorExportPlatformAppleEmbedded {
+	GDCLASS(EditorExportPlatformIOS, EditorExportPlatformAppleEmbedded);
 
-#include <sys/stat.h>
+	virtual String get_platform_name() const override { return "ios"; }
 
-// Optional environment variables for defining confidential information. If any
-// of these is set, they will override the values set in the credentials file.
-const String ENV_IOS_PROFILE_UUID_DEBUG = "GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG";
-const String ENV_IOS_PROFILE_UUID_RELEASE = "GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE";
-const String ENV_IOS_PROFILE_SPECIFIER_DEBUG = "GODOT_IOS_PROFILE_SPECIFIER_DEBUG";
-const String ENV_IOS_PROFILE_SPECIFIER_RELEASE = "GODOT_IOS_PROFILE_SPECIFIER_RELEASE";
+	virtual Vector<IconInfo> get_icon_infos() const override;
 
-class EditorExportPlatformIOS : public EditorExportPlatform {
-	GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
-
-	Ref<ImageTexture> logo;
-	Ref<ImageTexture> run_icon;
-
-	// Plugins
-	mutable SafeFlag plugins_changed;
-	SafeFlag devices_changed;
-
-	struct Device {
-		String id;
-		String name;
-		bool wifi = false;
-		bool use_ios_deploy = false;
-	};
-
-	Vector<Device> devices;
-	Mutex device_lock;
-
-	Mutex plugins_lock;
-	mutable Vector<PluginConfigIOS> plugins;
-#ifdef MACOS_ENABLED
-	Thread check_for_changes_thread;
-	SafeFlag quit_request;
-	SafeFlag has_runnable_preset;
-
-	static bool _check_xcode_install();
-	static void _check_for_changes_poll_thread(void *ud);
-	void _update_preset_status();
-#endif
-
-	typedef Error (*FileHandler)(String p_file, void *p_userdata);
-	static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata);
-	static Error _codesign(String p_file, void *p_userdata);
-	void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
-
-	struct IOSConfigData {
-		String pkg_name;
-		String binary_name;
-		String plist_content;
-		String architectures;
-		String linker_flags;
-		String cpp_code;
-		String modules_buildfile;
-		String modules_fileref;
-		String modules_buildphase;
-		String modules_buildgrp;
-		Vector<String> capabilities;
-		bool use_swift_runtime;
-	};
-	struct ExportArchitecture {
-		String name;
-		bool is_default = false;
-
-		ExportArchitecture() {}
-
-		ExportArchitecture(String p_name, bool p_is_default) {
-			name = p_name;
-			is_default = p_is_default;
-		}
-	};
-
-	struct IOSExportAsset {
-		String exported_path;
-		bool is_framework = false; // framework is anything linked to the binary, otherwise it's a resource
-		bool should_embed = false;
-	};
-
-	String _get_additional_plist_content();
-	String _get_linker_flags();
-	String _get_cpp_code();
-	void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug);
-	Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir);
-	Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir);
-
-	Vector<ExportArchitecture> _get_supported_architectures() const;
-	Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const;
-
-	void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const;
-	Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const;
-
-	void _add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
-	Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
-	Error _copy_asset(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);
-	Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
-	Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
-
-	Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_oneclick);
-
-	bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
-
-protected:
-	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
 	virtual void get_export_options(List<ExportOption> *r_options) const override;
-	virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
-	virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
+	virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
 
-	void _notification(int p_what);
+	virtual Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) override;
+	virtual Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir) override;
+	virtual HashMap<String, Variant> get_custom_project_settings(const Ref<EditorExportPreset> &p_preset) const override;
 
 public:
 	virtual String get_name() const override { return "iOS"; }
 	virtual String get_os_name() const override { return "iOS"; }
-	virtual Ref<Texture2D> get_logo() const override { return logo; }
-	virtual Ref<Texture2D> get_run_icon() const override { return run_icon; }
-
-	virtual int get_options_count() const override;
-	virtual String get_options_tooltip() const override;
-	virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
-	virtual String get_option_label(int p_index) const override;
-	virtual String get_option_tooltip(int p_index) const override;
-	virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
-
-	virtual bool poll_export() override {
-		bool dc = devices_changed.is_set();
-		if (dc) {
-			// don't clear unless we're reporting true, to avoid race
-			devices_changed.clear();
-		}
-		return dc;
-	}
-
-	virtual bool should_update_export_options() override {
-		bool export_options_changed = plugins_changed.is_set();
-		if (export_options_changed) {
-			// don't clear unless we're reporting true, to avoid race
-			plugins_changed.clear();
-		}
-		return export_options_changed;
-	}
-
-	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
-		List<String> list;
-		if (p_preset.is_valid()) {
-			bool project_only = p_preset->get("application/export_project_only");
-			if (project_only) {
-				list.push_back("xcodeproj");
-			} else {
-				list.push_back("ipa");
-			}
-		}
-		return list;
-	}
-
-	virtual HashMap<String, Variant> get_custom_project_settings(const Ref<EditorExportPreset> &p_preset) const override;
-
-	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
-
-	virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
-	virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
 
 	virtual void get_platform_features(List<String> *r_features) const override {
-		r_features->push_back("mobile");
+		EditorExportPlatformAppleEmbedded::get_platform_features(r_features);
 		r_features->push_back("ios");
 	}
 
-	virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override {
-	}
-
 	EditorExportPlatformIOS();
 	~EditorExportPlatformIOS();
-
-	/// List the gdip files in the directory specified by the p_path parameter.
-	static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
-		Vector<String> dir_files;
-		Ref<DirAccess> da = DirAccess::open(p_path);
-		if (da.is_valid()) {
-			da->list_dir_begin();
-			while (true) {
-				String file = da->get_next();
-				if (file.is_empty()) {
-					break;
-				}
-
-				if (file == "." || file == "..") {
-					continue;
-				}
-
-				if (da->current_is_hidden()) {
-					continue;
-				}
-
-				if (da->current_is_dir()) {
-					if (p_check_directories) {
-						Vector<String> directory_files = list_plugin_config_files(p_path.path_join(file), false);
-						for (int i = 0; i < directory_files.size(); ++i) {
-							dir_files.push_back(file.path_join(directory_files[i]));
-						}
-					}
-
-					continue;
-				}
-
-				if (file.ends_with(PluginConfigIOS::PLUGIN_CONFIG_EXT)) {
-					dir_files.push_back(file);
-				}
-			}
-			da->list_dir_end();
-		}
-
-		return dir_files;
-	}
-
-	static Vector<PluginConfigIOS> get_plugins() {
-		Vector<PluginConfigIOS> loaded_plugins;
-
-		String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().path_join("ios/plugins");
-
-		if (DirAccess::exists(plugins_dir)) {
-			Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true);
-
-			if (!plugins_filenames.is_empty()) {
-				Ref<ConfigFile> config_file = memnew(ConfigFile);
-				for (int i = 0; i < plugins_filenames.size(); i++) {
-					PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i]));
-					if (config.valid_config) {
-						loaded_plugins.push_back(config);
-					} else {
-						print_error("Invalid plugin config file " + plugins_filenames[i]);
-					}
-				}
-			}
-		}
-
-		return loaded_plugins;
-	}
-
-	static Vector<PluginConfigIOS> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
-		Vector<PluginConfigIOS> enabled_plugins;
-		Vector<PluginConfigIOS> all_plugins = get_plugins();
-		for (int i = 0; i < all_plugins.size(); i++) {
-			PluginConfigIOS plugin = all_plugins[i];
-			bool enabled = p_presets->get("plugins/" + plugin.name);
-			if (enabled) {
-				enabled_plugins.push_back(plugin);
-			}
-		}
-
-		return enabled_plugins;
-	}
 };

+ 37 - 0
platform/ios/godot_view_ios.h

@@ -0,0 +1,37 @@
+/**************************************************************************/
+/*  godot_view_ios.h                                                      */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/godot_view_apple_embedded.h"
+
+@interface GDTViewIOS : GDTView
+
+@end

+ 87 - 0
platform/ios/godot_view_ios.mm

@@ -0,0 +1,87 @@
+/**************************************************************************/
+/*  godot_view_ios.mm                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#import "godot_view_ios.h"
+
+#import "display_layer_ios.h"
+
+#include "core/error/error_macros.h"
+
+@interface GDTViewIOS ()
+
+GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wobjc-property-synthesis")
+@property(strong, nonatomic) CALayer<GDTDisplayLayer> *renderingLayer;
+GODOT_CLANG_WARNING_POP
+
+@end
+
+@implementation GDTViewIOS
+
+- (CALayer<GDTDisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+	if (self.renderingLayer) {
+		return self.renderingLayer;
+	}
+
+	CALayer<GDTDisplayLayer> *layer;
+
+	if ([driverName isEqualToString:@"vulkan"] || [driverName isEqualToString:@"metal"]) {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+		if (@available(iOS 13, *)) {
+			layer = [GDTMetalLayer layer];
+		} else {
+			return nil;
+		}
+#else
+		layer = [GDTMetalLayer layer];
+#endif
+	} else if ([driverName isEqualToString:@"opengl3"]) {
+		GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wdeprecated-declarations") // OpenGL is deprecated in iOS 12.0.
+		layer = [GDTOpenGLLayer layer];
+		GODOT_CLANG_WARNING_POP
+	} else {
+		return nil;
+	}
+
+	layer.frame = self.bounds;
+	layer.contentsScale = self.contentScaleFactor;
+
+	[self.layer addSublayer:layer];
+	self.renderingLayer = layer;
+
+	[layer initializeDisplayLayer];
+
+	return self.renderingLayer;
+}
+
+@end
+
+GDTView *GDTViewCreate() {
+	return [GDTViewIOS new];
+}

+ 3 - 25
platform/ios/ios.h

@@ -30,30 +30,8 @@
 
 #pragma once
 
-#include "core/object/class_db.h"
+#include "drivers/apple_embedded/apple_embedded.h"
 
-#import <CoreHaptics/CoreHaptics.h>
-
-class iOS : public Object {
-	GDCLASS(iOS, Object);
-
-	static void _bind_methods();
-
-private:
-	CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
-
-	CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
-	void start_haptic_engine();
-	void stop_haptic_engine();
-
-public:
-	static void alert(const char *p_alert, const char *p_title);
-
-	bool supports_haptic_engine();
-	void vibrate_haptic_engine(float p_duration_seconds, float p_amplitude);
-
-	String get_model() const;
-	String get_rate_url(int p_app_id) const;
-
-	iOS();
+class iOS : public AppleEmbedded {
+	GDCLASS(iOS, AppleEmbedded);
 };

+ 89 - 0
platform/ios/main_ios.mm

@@ -0,0 +1,89 @@
+/**************************************************************************/
+/*  main_ios.mm                                                           */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#import "os_ios.h"
+
+#import "drivers/apple_embedded/godot_app_delegate.h"
+#import "drivers/apple_embedded/main_utilities.h"
+#include "main/main.h"
+
+#import <UIKit/UIKit.h>
+#include <cstdio>
+
+int gargc;
+char **gargv;
+
+static OS_IOS *os = nullptr;
+
+int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+	//MoltenVK - enable full component swizzling support
+	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
+	gargc = argc;
+	gargv = argv;
+
+	@autoreleasepool {
+		NSString *className = NSStringFromClass([GDTApplicationDelegate class]);
+		UIApplicationMain(argc, argv, nil, className);
+	}
+	return 0;
+}
+
+int apple_embedded_main(int argc, char **argv) {
+	change_to_launch_dir(argv);
+
+	os = new OS_IOS();
+
+	// We must override main when testing is enabled
+	TEST_MAIN_OVERRIDE
+
+	char *fargv[64];
+	argc = process_args(argc, argv, fargv);
+
+	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
+
+	if (err != OK) {
+		if (err == ERR_HELP) { // Returned by --help and --version, so success.
+			return EXIT_SUCCESS;
+		}
+		return EXIT_FAILURE;
+	}
+
+	os->initialize_modules();
+
+	return os->get_exit_code();
+}
+
+void apple_embedded_finish() {
+	Main::cleanup();
+	delete os;
+}

+ 2 - 93
platform/ios/os_ios.h

@@ -32,107 +32,16 @@
 
 #ifdef IOS_ENABLED
 
-#import "ios.h"
-
-#import "drivers/apple/joypad_apple.h"
-#import "drivers/coreaudio/audio_driver_coreaudio.h"
-#include "drivers/unix/os_unix.h"
-#include "servers/audio_server.h"
-#include "servers/rendering/renderer_compositor.h"
-
-#if defined(RD_ENABLED)
-#include "servers/rendering/rendering_device.h"
-
-#if defined(VULKAN_ENABLED)
-#import "rendering_context_driver_vulkan_ios.h"
-#endif
-#endif
-
-class OS_IOS : public OS_Unix {
-private:
-	static HashMap<String, void *> dynamic_symbol_lookup_table;
-	friend void register_dynamic_symbol(char *name, void *address);
-
-	AudioDriverCoreAudio audio_driver;
-
-	iOS *ios = nullptr;
-
-	JoypadApple *joypad_apple = nullptr;
-
-	MainLoop *main_loop = nullptr;
-
-	virtual void initialize_core() override;
-	virtual void initialize() override;
-
-	virtual void initialize_joypads() override;
-
-	virtual void set_main_loop(MainLoop *p_main_loop) override;
-	virtual MainLoop *get_main_loop() const override;
-
-	virtual void delete_main_loop() override;
-
-	virtual void finalize() override;
-
-	bool is_focused = false;
-
-	CGFloat _weight_to_ct(int p_weight) const;
-	CGFloat _stretch_to_ct(int p_stretch) const;
-	String _get_default_fontname(const String &p_font_name) const;
-
-	static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
-
-	void deinitialize_modules();
+#import "drivers/apple_embedded/os_apple_embedded.h"
 
+class OS_IOS : public OS_AppleEmbedded {
 public:
 	static OS_IOS *get_singleton();
 
 	OS_IOS();
 	~OS_IOS();
 
-	void initialize_modules();
-
-	bool iterate();
-
-	void start();
-
-	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
-
-	virtual Vector<String> get_system_fonts() const override;
-	virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
-	virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
-
-	virtual Error open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data = nullptr) override;
-	virtual Error close_dynamic_library(void *p_library_handle) override;
-	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional = false) override;
-
 	virtual String get_name() const override;
-	virtual String get_distribution_name() const override;
-	virtual String get_version() const override;
-	virtual String get_model_name() const override;
-
-	virtual Error shell_open(const String &p_uri) override;
-
-	virtual String get_user_data_dir(const String &p_user_dir) const override;
-
-	virtual String get_cache_path() const override;
-	virtual String get_temp_path() const override;
-
-	virtual String get_locale() const override;
-
-	virtual String get_unique_id() const override;
-	virtual String get_processor_name() const override;
-
-	virtual void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0) override;
-
-	virtual bool _check_internal_feature_support(const String &p_feature) override;
-
-	void on_focus_out();
-	void on_focus_in();
-
-	void on_enter_background();
-	void on_exit_background();
-
-	virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override;
 };
 
 #endif // IOS_ENABLED

+ 4 - 675
platform/ios/os_ios.mm

@@ -30,694 +30,23 @@
 
 #import "os_ios.h"
 
-#ifdef IOS_ENABLED
-
-#import "app_delegate.h"
 #import "display_server_ios.h"
-#import "godot_view.h"
-#import "ios_terminal_logger.h"
-#import "view_controller.h"
-
-#include "core/config/project_settings.h"
-#include "core/io/dir_access.h"
-#include "core/io/file_access.h"
-#include "core/io/file_access_pack.h"
-#include "drivers/unix/syslog_logger.h"
-#include "main/main.h"
-
-#import <AudioToolbox/AudioServices.h>
-#import <CoreText/CoreText.h>
-#import <UIKit/UIKit.h>
-#import <dlfcn.h>
-#include <sys/sysctl.h>
-
-#if defined(RD_ENABLED)
-#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
-#import <QuartzCore/CAMetalLayer.h>
-
-#if defined(VULKAN_ENABLED)
-#include "drivers/vulkan/godot_vulkan.h"
-#endif // VULKAN_ENABLED
-#endif
-
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = nullptr;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-HashMap<String, void *> OS_IOS::dynamic_symbol_lookup_table;
-
-void add_ios_init_callback(init_callback cb) {
-	if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
-		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * (ios_init_callbacks_capacity + 32));
-		if (new_ptr) {
-			ios_init_callbacks = (init_callback *)(new_ptr);
-			ios_init_callbacks_capacity += 32;
-		} else {
-			ERR_FAIL_MSG("Unable to allocate memory for extension callbacks.");
-		}
-	}
-	ios_init_callbacks[ios_init_callbacks_count++] = cb;
-}
-
-void register_dynamic_symbol(char *name, void *address) {
-	OS_IOS::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Rect2 fit_keep_aspect_centered(const Vector2 &p_container, const Vector2 &p_rect) {
-	real_t available_ratio = p_container.width / p_container.height;
-	real_t fit_ratio = p_rect.width / p_rect.height;
-	Rect2 result;
-	if (fit_ratio < available_ratio) {
-		// Fit height - we'll have horizontal gaps
-		result.size.height = p_container.height;
-		result.size.width = p_container.height * fit_ratio;
-		result.position.y = 0;
-		result.position.x = (p_container.width - result.size.width) * 0.5f;
-	} else {
-		// Fit width - we'll have vertical gaps
-		result.size.width = p_container.width;
-		result.size.height = p_container.width / fit_ratio;
-		result.position.x = 0;
-		result.position.y = (p_container.height - result.size.height) * 0.5f;
-	}
-	return result;
-}
 
-Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) {
-	real_t available_ratio = p_container.width / p_container.height;
-	real_t fit_ratio = p_rect.width / p_rect.height;
-	Rect2 result;
-	if (fit_ratio < available_ratio) {
-		// Need to scale up to fit width, and crop height
-		result.size.width = p_container.width;
-		result.size.height = p_container.width / fit_ratio;
-		result.position.x = 0;
-		result.position.y = (p_container.height - result.size.height) * 0.5f;
-	} else {
-		// Need to scale up to fit height, and crop width
-		result.size.width = p_container.height * fit_ratio;
-		result.size.height = p_container.height;
-		result.position.x = (p_container.width - result.size.width) * 0.5f;
-		result.position.y = 0;
-	}
-	return result;
-}
+#ifdef IOS_ENABLED
 
 OS_IOS *OS_IOS::get_singleton() {
-	return (OS_IOS *)OS::get_singleton();
+	return (OS_IOS *)OS_AppleEmbedded::get_singleton();
 }
 
-OS_IOS::OS_IOS() {
-	for (int i = 0; i < ios_init_callbacks_count; ++i) {
-		ios_init_callbacks[i]();
-	}
-	free(ios_init_callbacks);
-	ios_init_callbacks = nullptr;
-	ios_init_callbacks_count = 0;
-	ios_init_callbacks_capacity = 0;
-
-	main_loop = nullptr;
-
-	Vector<Logger *> loggers;
-	loggers.push_back(memnew(IOSTerminalLogger));
-	_set_logger(memnew(CompositeLogger(loggers)));
-
-	AudioDriverManager::add_driver(&audio_driver);
-
+OS_IOS::OS_IOS() :
+		OS_AppleEmbedded() {
 	DisplayServerIOS::register_ios_driver();
 }
 
 OS_IOS::~OS_IOS() {}
 
-void OS_IOS::alert(const String &p_alert, const String &p_title) {
-	const CharString utf8_alert = p_alert.utf8();
-	const CharString utf8_title = p_title.utf8();
-	iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
-}
-
-void OS_IOS::initialize_core() {
-	OS_Unix::initialize_core();
-}
-
-void OS_IOS::initialize() {
-	initialize_core();
-}
-
-void OS_IOS::initialize_joypads() {
-	joypad_apple = memnew(JoypadApple);
-}
-
-void OS_IOS::initialize_modules() {
-	ios = memnew(iOS);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-}
-
-void OS_IOS::deinitialize_modules() {
-	if (joypad_apple) {
-		memdelete(joypad_apple);
-	}
-
-	if (ios) {
-		memdelete(ios);
-	}
-}
-
-void OS_IOS::set_main_loop(MainLoop *p_main_loop) {
-	main_loop = p_main_loop;
-}
-
-MainLoop *OS_IOS::get_main_loop() const {
-	return main_loop;
-}
-
-void OS_IOS::delete_main_loop() {
-	if (main_loop) {
-		main_loop->finalize();
-		memdelete(main_loop);
-	}
-
-	main_loop = nullptr;
-}
-
-bool OS_IOS::iterate() {
-	if (!main_loop) {
-		return true;
-	}
-
-	if (DisplayServer::get_singleton()) {
-		DisplayServer::get_singleton()->process_events();
-	}
-
-	joypad_apple->process_joypads();
-
-	return Main::iteration();
-}
-
-void OS_IOS::start() {
-	if (Main::start() == EXIT_SUCCESS) {
-		main_loop->initialize();
-	}
-}
-
-void OS_IOS::finalize() {
-	deinitialize_modules();
-
-	// Already gets called
-	//delete_main_loop();
-}
-
-// MARK: Dynamic Libraries
-
-_FORCE_INLINE_ String OS_IOS::get_framework_executable(const String &p_path) {
-	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
-
-	// Read framework bundle to get executable name.
-	NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
-	NSBundle *bundle = [NSBundle bundleWithURL:url];
-	if (bundle) {
-		String exe_path = String::utf8([[bundle executablePath] UTF8String]);
-		if (da->file_exists(exe_path)) {
-			return exe_path;
-		}
-	}
-
-	// Try default executable name (invalid framework).
-	if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
-		return p_path.path_join(p_path.get_file().get_basename());
-	}
-
-	// Not a framework, try loading as .dylib.
-	return p_path;
-}
-
-Error OS_IOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
-	if (p_path.length() == 0) {
-		// Static xcframework.
-		p_library_handle = RTLD_SELF;
-
-		if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
-			*p_data->r_resolved_path = p_path;
-		}
-
-		return OK;
-	}
-
-	String path = get_framework_executable(p_path);
-
-	if (!FileAccess::exists(path)) {
-		// Load .dylib or framework from within the executable path.
-		path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
-	}
-
-	if (!FileAccess::exists(path)) {
-		// Load .dylib converted to framework from within the executable path.
-		path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file().get_basename() + ".framework"));
-	}
-
-	if (!FileAccess::exists(path)) {
-		// Load .dylib or framework from a standard iOS location.
-		path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file()));
-	}
-
-	if (!FileAccess::exists(path)) {
-		// Load .dylib converted to framework from a standard iOS location.
-		path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework"));
-	}
-
-	ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
-
-	p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
-	ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
-
-	if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
-		*p_data->r_resolved_path = path;
-	}
-
-	return OK;
-}
-
-Error OS_IOS::close_dynamic_library(void *p_library_handle) {
-	if (p_library_handle == RTLD_SELF) {
-		return OK;
-	}
-	return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-Error OS_IOS::get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional) {
-	if (p_library_handle == RTLD_SELF) {
-		void **ptr = OS_IOS::dynamic_symbol_lookup_table.getptr(p_name);
-		if (ptr) {
-			p_symbol_handle = *ptr;
-			return OK;
-		}
-	}
-	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
-}
-
 String OS_IOS::get_name() const {
 	return "iOS";
 }
 
-String OS_IOS::get_distribution_name() const {
-	return get_name();
-}
-
-String OS_IOS::get_version() const {
-	NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
-	return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
-}
-
-String OS_IOS::get_model_name() const {
-	String model = ios->get_model();
-	if (model != "") {
-		return model;
-	}
-
-	return OS_Unix::get_model_name();
-}
-
-Error OS_IOS::shell_open(const String &p_uri) {
-	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-	NSURL *url = [NSURL URLWithString:urlPath];
-
-	if (![[UIApplication sharedApplication] canOpenURL:url]) {
-		return ERR_CANT_OPEN;
-	}
-
-	print_verbose(vformat("Opening URL %s", p_uri));
-
-	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
-
-	return OK;
-}
-
-String OS_IOS::get_user_data_dir(const String &p_user_dir) const {
-	static String ret;
-	if (ret.is_empty()) {
-		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
-		if (paths && [paths count] >= 1) {
-			ret.append_utf8([[paths firstObject] UTF8String]);
-		}
-	}
-	return ret;
-}
-
-String OS_IOS::get_cache_path() const {
-	static String ret;
-	if (ret.is_empty()) {
-		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
-		if (paths && [paths count] >= 1) {
-			ret.append_utf8([[paths firstObject] UTF8String]);
-		}
-	}
-	return ret;
-}
-
-String OS_IOS::get_temp_path() const {
-	static String ret;
-	if (ret.is_empty()) {
-		NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
-								isDirectory:YES];
-		if (url) {
-			ret = String::utf8([url.path UTF8String]);
-			ret = ret.trim_prefix("file://");
-		}
-	}
-	return ret;
-}
-
-String OS_IOS::get_locale() const {
-	NSString *preferredLanguage = [NSLocale preferredLanguages].firstObject;
-
-	if (preferredLanguage) {
-		return String::utf8([preferredLanguage UTF8String]).replace_char('-', '_');
-	}
-
-	NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier];
-	return String::utf8([localeIdentifier UTF8String]).replace_char('-', '_');
-}
-
-String OS_IOS::get_unique_id() const {
-	NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
-	return String::utf8([uuid UTF8String]);
-}
-
-String OS_IOS::get_processor_name() const {
-	char buffer[256];
-	size_t buffer_len = 256;
-	if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
-		return String::utf8(buffer, buffer_len);
-	}
-	ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
-}
-
-Vector<String> OS_IOS::get_system_fonts() const {
-	HashSet<String> font_names;
-	CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
-	if (fonts) {
-		for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
-			CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
-			if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
-				NSString *ns_name = (__bridge NSString *)cf_name;
-				font_names.insert(String::utf8([ns_name UTF8String]));
-			}
-		}
-		CFRelease(fonts);
-	}
-
-	Vector<String> ret;
-	for (const String &E : font_names) {
-		ret.push_back(E);
-	}
-	return ret;
-}
-
-String OS_IOS::_get_default_fontname(const String &p_font_name) const {
-	String font_name = p_font_name;
-	if (font_name.to_lower() == "sans-serif") {
-		font_name = "Helvetica";
-	} else if (font_name.to_lower() == "serif") {
-		font_name = "Times";
-	} else if (font_name.to_lower() == "monospace") {
-		font_name = "Courier";
-	} else if (font_name.to_lower() == "fantasy") {
-		font_name = "Papyrus";
-	} else if (font_name.to_lower() == "cursive") {
-		font_name = "Apple Chancery";
-	};
-	return font_name;
-}
-
-CGFloat OS_IOS::_weight_to_ct(int p_weight) const {
-	if (p_weight < 150) {
-		return -0.80;
-	} else if (p_weight < 250) {
-		return -0.60;
-	} else if (p_weight < 350) {
-		return -0.40;
-	} else if (p_weight < 450) {
-		return 0.0;
-	} else if (p_weight < 550) {
-		return 0.23;
-	} else if (p_weight < 650) {
-		return 0.30;
-	} else if (p_weight < 750) {
-		return 0.40;
-	} else if (p_weight < 850) {
-		return 0.56;
-	} else if (p_weight < 925) {
-		return 0.62;
-	} else {
-		return 1.00;
-	}
-}
-
-CGFloat OS_IOS::_stretch_to_ct(int p_stretch) const {
-	if (p_stretch < 56) {
-		return -0.5;
-	} else if (p_stretch < 69) {
-		return -0.37;
-	} else if (p_stretch < 81) {
-		return -0.25;
-	} else if (p_stretch < 93) {
-		return -0.13;
-	} else if (p_stretch < 106) {
-		return 0.0;
-	} else if (p_stretch < 137) {
-		return 0.13;
-	} else if (p_stretch < 144) {
-		return 0.25;
-	} else if (p_stretch < 162) {
-		return 0.37;
-	} else {
-		return 0.5;
-	}
-}
-
-Vector<String> OS_IOS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const {
-	Vector<String> ret;
-	String font_name = _get_default_fontname(p_font_name);
-
-	CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
-	CTFontSymbolicTraits traits = 0;
-	if (p_weight >= 700) {
-		traits |= kCTFontBoldTrait;
-	}
-	if (p_italic) {
-		traits |= kCTFontItalicTrait;
-	}
-	if (p_stretch < 100) {
-		traits |= kCTFontCondensedTrait;
-	} else if (p_stretch > 100) {
-		traits |= kCTFontExpandedTrait;
-	}
-
-	CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
-	CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
-	CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
-
-	CGFloat weight = _weight_to_ct(p_weight);
-	CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
-	CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
-
-	CGFloat stretch = _stretch_to_ct(p_stretch);
-	CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
-	CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
-
-	CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
-	CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
-	CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
-
-	CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
-	if (font) {
-		CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
-		if (family) {
-			CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
-			CFRange range = CFRangeMake(0, CFStringGetLength(string));
-			CTFontRef fallback_family = CTFontCreateForString(family, string, range);
-			if (fallback_family) {
-				CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
-				if (fallback_font) {
-					CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
-					if (url) {
-						NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
-						ret.push_back(String::utf8([font_path UTF8String]));
-						CFRelease(url);
-					}
-					CFRelease(fallback_font);
-				}
-				CFRelease(fallback_family);
-			}
-			CFRelease(string);
-			CFRelease(family);
-		}
-		CFRelease(font);
-	}
-
-	CFRelease(attributes);
-	CFRelease(traits_dict);
-	CFRelease(sym_traits);
-	CFRelease(font_stretch);
-	CFRelease(font_weight);
-	CFRelease(name);
-
-	return ret;
-}
-
-String OS_IOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
-	String ret;
-	String font_name = _get_default_fontname(p_font_name);
-
-	CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
-
-	CTFontSymbolicTraits traits = 0;
-	if (p_weight >= 700) {
-		traits |= kCTFontBoldTrait;
-	}
-	if (p_italic) {
-		traits |= kCTFontItalicTrait;
-	}
-	if (p_stretch < 100) {
-		traits |= kCTFontCondensedTrait;
-	} else if (p_stretch > 100) {
-		traits |= kCTFontExpandedTrait;
-	}
-
-	CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
-	CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
-	CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
-
-	CGFloat weight = _weight_to_ct(p_weight);
-	CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
-	CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
-
-	CGFloat stretch = _stretch_to_ct(p_stretch);
-	CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
-	CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
-
-	CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
-	CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
-	CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
-
-	CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
-	if (font) {
-		CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
-		if (url) {
-			NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
-			ret = String::utf8([font_path UTF8String]);
-			CFRelease(url);
-		}
-		CFRelease(font);
-	}
-
-	CFRelease(attributes);
-	CFRelease(traits_dict);
-	CFRelease(sym_traits);
-	CFRelease(font_stretch);
-	CFRelease(font_weight);
-	CFRelease(name);
-
-	return ret;
-}
-
-void OS_IOS::vibrate_handheld(int p_duration_ms, float p_amplitude) {
-	if (ios->supports_haptic_engine()) {
-		if (p_amplitude > 0.0) {
-			p_amplitude = CLAMP(p_amplitude, 0.0, 1.0);
-		}
-
-		ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f, p_amplitude);
-	} else {
-		// iOS <13 does not support duration for vibration
-		AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-	}
-}
-
-bool OS_IOS::_check_internal_feature_support(const String &p_feature) {
-	if (p_feature == "system_fonts") {
-		return true;
-	}
-	if (p_feature == "mobile") {
-		return true;
-	}
-
-	return false;
-}
-
-void OS_IOS::on_focus_out() {
-	if (is_focused) {
-		is_focused = false;
-
-		if (DisplayServerIOS::get_singleton()) {
-			DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
-		}
-
-		if (OS::get_singleton()->get_main_loop()) {
-			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
-		}
-
-		[AppDelegate.viewController.godotView stopRendering];
-
-		audio_driver.stop();
-	}
-}
-
-void OS_IOS::on_focus_in() {
-	if (!is_focused) {
-		is_focused = true;
-
-		if (DisplayServerIOS::get_singleton()) {
-			DisplayServerIOS::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
-		}
-
-		if (OS::get_singleton()->get_main_loop()) {
-			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
-		}
-
-		[AppDelegate.viewController.godotView startRendering];
-
-		audio_driver.start();
-	}
-}
-
-void OS_IOS::on_enter_background() {
-	// Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive.
-
-	if (OS::get_singleton()->get_main_loop()) {
-		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
-	}
-
-	on_focus_out();
-}
-
-void OS_IOS::on_exit_background() {
-	if (!is_focused) {
-		on_focus_in();
-
-		if (OS::get_singleton()->get_main_loop()) {
-			OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
-		}
-	}
-}
-
-Rect2 OS_IOS::calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const {
-	String scalemodestr = GLOBAL_GET("ios/launch_screen_image_mode");
-
-	if (scalemodestr == "scaleAspectFit") {
-		return fit_keep_aspect_centered(p_window_size, p_imgrect_size);
-	} else if (scalemodestr == "scaleAspectFill") {
-		return fit_keep_aspect_covered(p_window_size, p_imgrect_size);
-	} else if (scalemodestr == "scaleToFill") {
-		return Rect2(Point2(), p_window_size);
-	} else if (scalemodestr == "center") {
-		return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
-	} else {
-		WARN_PRINT(vformat("Boot screen scale mode mismatch between iOS and Godot: %s not supported", scalemodestr));
-		return OS_Unix::calculate_boot_screen_rect(p_window_size, p_imgrect_size);
-	}
-}
-
 #endif // IOS_ENABLED

+ 1 - 12
platform/ios/platform_config.h

@@ -30,15 +30,4 @@
 
 #pragma once
 
-#include <alloca.h>
-
-#define PLATFORM_THREAD_OVERRIDE
-
-#define PTHREAD_RENAME_SELF
-
-#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
-#define _strongify(var)                                      \
-	_Pragma("clang diagnostic push")                         \
-			_Pragma("clang diagnostic ignored \"-Wshadow\"") \
-					__strong typeof(var) var = GDWeak_##var; \
-	_Pragma("clang diagnostic pop")
+#import "drivers/apple_embedded/platform_config.h"

+ 2 - 62
platform/ios/platform_ios_builders.py

@@ -1,67 +1,7 @@
 """Functions used to generate source files during build time"""
 
-import os
-import shutil
-
-from platform_methods import detect_mvk, lipo
-
-
-def combine_libs(target, source, env):
-    lib_path = target[0].srcnode().abspath
-    if "osxcross" in env:
-        libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}libtool"
-    else:
-        libtool = "$IOS_TOOLCHAIN_PATH/usr/bin/libtool"
-    env.Execute(
-        libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
-    )
+from platform_methods import generate_bundle_apple_embedded
 
 
 def generate_bundle(target, source, env):
-    bin_dir = env.Dir("#bin").abspath
-
-    # Template bundle.
-    app_prefix = "godot." + env["platform"]
-    rel_prefix = "libgodot." + env["platform"] + "." + "template_release"
-    dbg_prefix = "libgodot." + env["platform"] + "." + "template_debug"
-    if env.dev_build:
-        app_prefix += ".dev"
-        rel_prefix += ".dev"
-        dbg_prefix += ".dev"
-    if env["precision"] == "double":
-        app_prefix += ".double"
-        rel_prefix += ".double"
-        dbg_prefix += ".double"
-
-    # Lipo template libraries.
-    rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + ".a")
-    dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + ".a")
-    rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + env.extra_suffix + ".a")
-    dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + env.extra_suffix + ".a")
-
-    # Assemble Xcode project bundle.
-    app_dir = env.Dir("#bin/ios_xcode").abspath
-    templ = env.Dir("#misc/dist/ios_xcode").abspath
-    if os.path.exists(app_dir):
-        shutil.rmtree(app_dir)
-    shutil.copytree(templ, app_dir)
-    if rel_target_bin != "":
-        shutil.copy(rel_target_bin, app_dir + "/libgodot.ios.release.xcframework/ios-arm64/libgodot.a")
-    if dbg_target_bin != "":
-        shutil.copy(dbg_target_bin, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a")
-    if rel_target_bin_sim != "":
-        shutil.copy(
-            rel_target_bin_sim, app_dir + "/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
-        )
-    if dbg_target_bin_sim != "":
-        shutil.copy(
-            dbg_target_bin_sim, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
-        )
-    mvk_path = detect_mvk(env, "ios-arm64")
-    if mvk_path != "":
-        shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework")
-
-    # ZIP Xcode project bundle.
-    zip_dir = env.Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
-    shutil.make_archive(zip_dir, "zip", root_dir=app_dir)
-    shutil.rmtree(app_dir)
+    generate_bundle_apple_embedded("ios", "ios-arm64", "ios-arm64_x86_64-simulator", True, target, source, env)

+ 2 - 0
platform/macos/README.md

@@ -3,6 +3,8 @@
 This folder contains the C++, Objective-C and Objective-C++ code for the macOS
 platform port.
 
+This platform uses shared Apple code ([`drivers/apple`](/drivers/apple)).
+
 See also [`misc/dist/macos`](/misc/dist/macos) folder for additional files used
 by this platform. [`misc/dist/macos_tools.app`](/misc/dist/macos_tools.app) is
 an `.app` bundle template used for packaging the macOS editor, while

+ 77 - 0
platform_methods.py

@@ -1,5 +1,6 @@
 import os
 import platform
+import shutil
 import subprocess
 import sys
 
@@ -160,3 +161,79 @@ def detect_mvk(env, osname):
             return mvk_path
 
     return ""
+
+
+def combine_libs_apple_embedded(target, source, env):
+    lib_path = target[0].srcnode().abspath
+    if "osxcross" in env:
+        libtool = "$APPLE_TOOLCHAIN_PATH/usr/bin/${apple_target_triple}libtool"
+    else:
+        libtool = "$APPLE_TOOLCHAIN_PATH/usr/bin/libtool"
+    env.Execute(
+        libtool + ' -static -o "' + lib_path + '" ' + " ".join([('"' + lib.srcnode().abspath + '"') for lib in source])
+    )
+
+
+def generate_bundle_apple_embedded(platform, framework_dir, framework_dir_sim, use_mkv, target, source, env):
+    bin_dir = env.Dir("#bin").abspath
+
+    # Template bundle.
+    app_prefix = "godot." + platform
+    rel_prefix = "libgodot." + platform + "." + "template_release"
+    dbg_prefix = "libgodot." + platform + "." + "template_debug"
+    if env.dev_build:
+        app_prefix += ".dev"
+        rel_prefix += ".dev"
+        dbg_prefix += ".dev"
+    if env["precision"] == "double":
+        app_prefix += ".double"
+        rel_prefix += ".double"
+        dbg_prefix += ".double"
+
+    # Lipo template libraries.
+    #
+    # env.extra_suffix contains ".simulator" when building for simulator,
+    # but it's undesired when calling lipo()
+    extra_suffix = env.extra_suffix.replace(".simulator", "")
+    rel_target_bin = lipo(bin_dir + "/" + rel_prefix, extra_suffix + ".a")
+    dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, extra_suffix + ".a")
+    rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + extra_suffix + ".a")
+    dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + extra_suffix + ".a")
+    # Assemble Xcode project bundle.
+    app_dir = env.Dir("#bin/" + platform + "_xcode").abspath
+    templ = env.Dir("#misc/dist/" + platform + "_xcode").abspath
+    if os.path.exists(app_dir):
+        shutil.rmtree(app_dir)
+    shutil.copytree(templ, app_dir)
+    if rel_target_bin != "":
+        print(f' Copying "{platform}" release framework')
+        shutil.copy(
+            rel_target_bin, app_dir + "/libgodot." + platform + ".release.xcframework/" + framework_dir + "/libgodot.a"
+        )
+    if dbg_target_bin != "":
+        print(f' Copying "{platform}" debug framework')
+        shutil.copy(
+            dbg_target_bin, app_dir + "/libgodot." + platform + ".debug.xcframework/" + framework_dir + "/libgodot.a"
+        )
+    if rel_target_bin_sim != "":
+        print(f' Copying "{platform}" (simulator) release framework')
+        shutil.copy(
+            rel_target_bin_sim,
+            app_dir + "/libgodot." + platform + ".release.xcframework/" + framework_dir_sim + "/libgodot.a",
+        )
+    if dbg_target_bin_sim != "":
+        print(f' Copying "{platform}" (simulator) debug framework')
+        shutil.copy(
+            dbg_target_bin_sim,
+            app_dir + "/libgodot." + platform + ".debug.xcframework/" + framework_dir_sim + "/libgodot.a",
+        )
+
+    if use_mkv:
+        mvk_path = detect_mvk(env, "ios-arm64")
+        if mvk_path != "":
+            shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework")
+
+    # ZIP Xcode project bundle.
+    zip_dir = env.Dir("#bin/" + (app_prefix + extra_suffix).replace(".", "_")).abspath
+    shutil.make_archive(zip_dir, "zip", root_dir=app_dir)
+    shutil.rmtree(app_dir)

+ 1 - 1
scene/3d/lightmap_gi.cpp

@@ -1791,7 +1791,7 @@ PackedStringArray LightmapGI::get_configuration_warnings() const {
 		warnings.push_back(RTR("The lightmap has no baked shadowmask textures. Please rebake with the Shadowmask Mode set to anything other than None."));
 	}
 
-#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+#elif defined(ANDROID_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 	warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
 #else
 	warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));

+ 7 - 7
servers/rendering/renderer_rd/environment/fog.cpp

@@ -435,7 +435,7 @@ void Fog::VolumetricFog::init(const Vector3i &fog_size, RID p_sky_shader) {
 	fog_map = RD::get_singleton()->texture_create(tf, RD::TextureView());
 	RD::get_singleton()->set_resource_name(fog_map, "Fog map");
 
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 	Vector<uint8_t> dm;
 	dm.resize_zeroed(fog_size.x * fog_size.y * fog_size.z * 4);
 
@@ -574,7 +574,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 
 			{
 				RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 				u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
@@ -594,7 +594,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 
 			{
 				RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 				u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
@@ -606,7 +606,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 
 			{
 				RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 				u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
@@ -913,7 +913,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 		}
 		{
 			RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 			u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
@@ -924,7 +924,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 		}
 		{
 			RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 			u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
@@ -936,7 +936,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
 
 		{
 			RD::Uniform u;
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
 			u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 #else
 			u.uniform_type = RD::UNIFORM_TYPE_IMAGE;

+ 1 - 1
servers/rendering/renderer_rd/shader_rd.cpp

@@ -227,7 +227,7 @@ void ShaderRD::_build_variant_code(StringBuilder &builder, uint32_t p_variant, c
 				for (const KeyValue<StringName, CharString> &E : p_version->code_sections) {
 					builder.append(String("#define ") + String(E.key) + "_CODE_USED\n");
 				}
-#if (defined(MACOS_ENABLED) || defined(IOS_ENABLED))
+#if (defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED))
 				if (RD::get_singleton()->get_device_capabilities().device_family == RDD::DEVICE_VULKAN) {
 					builder.append("#define MOLTENVK_USED\n");
 				}

+ 1 - 1
servers/rendering/renderer_rd/storage_rd/utilities.cpp

@@ -265,7 +265,7 @@ bool Utilities::has_os_feature(const String &p_feature) const {
 		return true;
 	}
 
-#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED)
+#if !defined(ANDROID_ENABLED) && !defined(APPLE_EMBEDDED_ENABLED)
 	// Some Android devices report support for S3TC but we don't expect that and don't export the textures.
 	// This could be fixed but so few devices support it that it doesn't seem useful (and makes bigger APKs).
 	// For good measure we do the same hack for iOS, just in case.

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно