Browse Source

Introduce 'drivers/apple_embedded' abstract platform for code reuse

Ricardo Sanchez-Saez 4 tháng trước cách đây
mục cha
commit
457299449d
84 tập tin đã thay đổi với 6102 bổ sung3459 xóa
  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.

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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.

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác