Browse Source

Merge pull request #40434 from naithar/feature/ios-moltenVK

[iOS] Basic Vulkan/Metal Support
Rémi Verschelde 5 years ago
parent
commit
9856c8fda4
46 changed files with 3724 additions and 2629 deletions
  1. 50 0
      .github/workflows/ios_builds.yml
  2. 1 1
      .github/workflows/macos_builds.yml
  3. 2 2
      core/callable_method_pointer.h
  4. 2 2
      core/variant.cpp
  5. 11 10
      drivers/vulkan/SCsub
  6. 10 2
      misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
  7. 7 0
      misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json
  8. 17 17
      modules/arkit/arkit_interface.h
  9. 206 165
      modules/arkit/arkit_interface.mm
  10. 3 3
      modules/arkit/arkit_session_delegate.h
  11. 57 36
      modules/camera/camera_ios.mm
  12. 7 4
      platform/iphone/SCsub
  13. 5 14
      platform/iphone/app_delegate.h
  14. 34 625
      platform/iphone/app_delegate.mm
  15. 13 11
      platform/iphone/detect.py
  16. 58 0
      platform/iphone/display_layer.h
  17. 186 0
      platform/iphone/display_layer.mm
  18. 202 0
      platform/iphone/display_server_iphone.h
  19. 751 0
      platform/iphone/display_server_iphone.mm
  20. 3 3
      platform/iphone/game_center.h
  21. 36 25
      platform/iphone/game_center.mm
  22. 0 123
      platform/iphone/gl_view.h
  23. 0 702
      platform/iphone/gl_view.mm
  24. 47 9
      platform/iphone/godot_iphone.mm
  25. 56 0
      platform/iphone/godot_view.h
  26. 498 0
      platform/iphone/godot_view.mm
  27. 44 0
      platform/iphone/godot_view_renderer.h
  28. 146 0
      platform/iphone/godot_view_renderer.mm
  29. 3 3
      platform/iphone/icloud.h
  30. 14 16
      platform/iphone/icloud.mm
  31. 2 2
      platform/iphone/in_app_store.h
  32. 29 11
      platform/iphone/in_app_store.mm
  33. 14 4
      platform/iphone/ios.mm
  34. 50 0
      platform/iphone/joypad_iphone.h
  35. 380 0
      platform/iphone/joypad_iphone.mm
  36. 10 5
      platform/iphone/main.m
  37. 0 632
      platform/iphone/os_iphone.cpp
  38. 38 113
      platform/iphone/os_iphone.h
  39. 369 0
      platform/iphone/os_iphone.mm
  40. 9 11
      platform/iphone/view_controller.h
  41. 309 47
      platform/iphone/view_controller.mm
  42. 3 2
      platform/iphone/vulkan_context_iphone.h
  43. 12 10
      platform/iphone/vulkan_context_iphone.mm
  44. 1 1
      scene/gui/scroll_bar.cpp
  45. 29 0
      thirdparty/vulkan/patches/VMA-assert-remove.patch
  46. 0 18
      thirdparty/vulkan/vk_mem_alloc.h

+ 50 - 0
.github/workflows/ios_builds.yml

@@ -0,0 +1,50 @@
+name: iOS Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+  GODOT_BASE_BRANCH: master
+  SCONS_CACHE_LIMIT: 4096
+
+jobs:
+  ios-template:
+    runs-on: "macos-latest"
+    name: Template (target=release, tools=no)
+
+    steps:
+      - uses: actions/checkout@v2
+
+      # Upload cache on completion and check it out now
+      - name: Load .scons_cache directory
+        id: ios-template-cache
+        uses: actions/cache@v2
+        with:
+          path: ${{github.workspace}}/.scons_cache/
+          key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+          restore-keys: |
+            ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+            ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+            ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+      # Use python 3.x release (works cross platform)
+      - name: Set up Python 3.x
+        uses: actions/setup-python@v2
+        with:
+          # Semantic version range syntax or exact version of a Python version
+          python-version: '3.x'
+          # Optional - x64 or x86 architecture, defaults to x64
+          architecture: 'x64'
+
+      # You can test your matrix by printing the current Python version
+      - name: Configuring Python packages
+        run: |
+          python -c "import sys; print(sys.version)"
+          python -m pip install scons
+          python --version
+          scons --version
+
+      - name: Compilation
+        env:
+          SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+        run: |
+          scons -j2 verbose=yes warnings=all werror=yes platform=iphone target=release tools=no

+ 1 - 1
.github/workflows/macos_builds.yml

@@ -1,4 +1,4 @@
-name: MacOS Builds
+name: macOS Builds
 on: [push, pull_request]
 
 # Global Cache Settings

+ 2 - 2
core/callable_method_pointer.h

@@ -131,7 +131,7 @@ void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), con
 #ifdef DEBUG_METHODS_ENABLED
 	(p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
 #else
-	(p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+	(p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
 #endif
 }
 
@@ -228,7 +228,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co
 #ifdef DEBUG_METHODS_ENABLED
 	r_ret = (p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
 #else
-	(p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+	(p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
 #endif
 }
 

+ 2 - 2
core/variant.cpp

@@ -1454,7 +1454,7 @@ Variant::operator signed long() const {
 		case INT:
 			return _data._int;
 		case FLOAT:
-			return _data._real;
+			return _data._float;
 		case STRING:
 			return operator String().to_int();
 		default: {
@@ -1474,7 +1474,7 @@ Variant::operator unsigned long() const {
 		case INT:
 			return _data._int;
 		case FLOAT:
-			return _data._real;
+			return _data._float;
 		case STRING:
 			return operator String().to_int();
 		default: {

+ 11 - 10
drivers/vulkan/SCsub

@@ -22,6 +22,17 @@ if env["platform"] == "android":
     thirdparty_dir = "#thirdparty/vulkan"
     vma_sources = [thirdparty_dir + "/android/vk_mem_alloc.cpp"]
     env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
+elif env["platform"] == "iphone":
+    # Use bundled Vulkan headers
+    thirdparty_dir = "#thirdparty/vulkan"
+    env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include", thirdparty_dir + "/loader"])
+
+    # Build Vulkan memory allocator
+    env_thirdparty = env.Clone()
+    env_thirdparty.disable_warnings()
+
+    vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"]
+    env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
 elif env["builtin_vulkan"]:
     # Use bundled Vulkan headers
     thirdparty_dir = "#thirdparty/vulkan"
@@ -70,16 +81,6 @@ elif env["builtin_vulkan"]:
                 'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
             ]
         )
-    elif env["platform"] == "iphone":
-        env_thirdparty.AppendUnique(
-            CPPDEFINES=[
-                "VK_USE_PLATFORM_IOS_MVK",
-                "VULKAN_NON_CMAKE_BUILD",
-                'SYSCONFDIR=\\"%s\\"' % "/etc",
-                'FALLBACK_DATA_DIRS=\\"%s\\"' % "/usr/local/share:/usr/share",
-                'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
-            ]
-        )
     elif env["platform"] == "linuxbsd":
         env_thirdparty.AppendUnique(
             CPPDEFINES=[

+ 10 - 2
misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj

@@ -12,6 +12,8 @@
 		$modules_buildfile
 		1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; };
 		D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D07CD44D1C5D589C00B7FB28 /* Images.xcassets */; };
+		9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9039D3BD24C093AC0020482C /* MoltenVK.a */; };
+		9073252C24BF980B0063BCD4 /* vulkan in Resources */ = {isa = PBXBuildFile; fileRef = 905036DC24BF932E00301046 /* vulkan */; };
 		D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; };
 		D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; };
 		$pbx_launch_screen_build_reference
@@ -37,6 +39,8 @@
 		$modules_fileref
 		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.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = MoltenVK; path = MoltenVK.a; sourceTree = "<group>"; };
+		905036DC24BF932E00301046 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vulkan; path = "$binary/vulkan"; sourceTree = "<group>"; };
 		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>"; };
@@ -52,6 +56,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */,
 				DEADBEEF2F582BE20003B888 /* $binary.a */,
 				$modules_buildphase
 				$additional_pbx_frameworks_build
@@ -64,6 +69,7 @@
 		D0BCFE2B18AEBDA2004A7AAE = {
 			isa = PBXGroup;
 			children = (
+				905036DC24BF932E00301046 /* vulkan */,
 				1F1575711F582BE20003B888 /* dylibs */,
 				D0BCFE7718AEBFEB004A7AAE /* $binary.pck */,
 				D0BCFE4118AEBDA2004A7AAE /* $binary */,
@@ -84,6 +90,7 @@
 		D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				9039D3BD24C093AC0020482C /* MoltenVK.a */,
 				DEADBEEF1F582BE20003B888 /* $binary.a */,
 				$modules_buildgrp
 				$additional_pbx_frameworks_refs
@@ -248,6 +255,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				9073252C24BF980B0063BCD4 /* vulkan in Resources */,
 				D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */,
 				D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */,
 				$pbx_launch_screen_build_phase
@@ -319,7 +327,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
 				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";
@@ -358,7 +366,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
 				OTHER_LDFLAGS = "$linker_flags";
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";

+ 7 - 0
misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json

@@ -0,0 +1,7 @@
+{
+    "file_format_version" : "1.0.0",
+    "ICD": {
+        "library_path": "./../../Frameworks/MoltenVK.framework/MoltenVK",
+        "api_version" : "1.0.0"
+    }
+}

+ 17 - 17
modules/arkit/arkit_interface.h

@@ -60,8 +60,8 @@ private:
 	float eye_height, z_near, z_far;
 
 	Ref<CameraFeed> feed;
-	int image_width[2];
-	int image_height[2];
+	size_t image_width[2];
+	size_t image_height[2];
 	Vector<uint8_t> img_data[2];
 
 	struct anchor_map {
@@ -84,9 +84,9 @@ public:
 	void start_session();
 	void stop_session();
 
-	bool get_anchor_detection_is_enabled() const;
-	void set_anchor_detection_is_enabled(bool p_enable);
-	virtual int get_camera_feed_id();
+	bool get_anchor_detection_is_enabled() const override;
+	void set_anchor_detection_is_enabled(bool p_enable) override;
+	virtual int get_camera_feed_id() override;
 
 	bool get_light_estimation_is_enabled() const;
 	void set_light_estimation_is_enabled(bool p_enable);
@@ -97,22 +97,22 @@ public:
 	/* while Godot has its own raycast logic this takes ARKits camera into account and hits on any ARAnchor */
 	Array raycast(Vector2 p_screen_coord);
 
-	void notification(int p_what);
+	virtual void notification(int p_what) override;
 
-	virtual StringName get_name() const;
-	virtual int get_capabilities() const;
+	virtual StringName get_name() const override;
+	virtual int get_capabilities() const override;
 
-	virtual bool is_initialized() const;
-	virtual bool initialize();
-	virtual void uninitialize();
+	virtual bool is_initialized() const override;
+	virtual bool initialize() override;
+	virtual void uninitialize() override;
 
-	virtual Size2 get_render_targetsize();
-	virtual bool is_stereo();
-	virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform);
-	virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far);
-	virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect);
+	virtual Size2 get_render_targetsize() override;
+	virtual bool is_stereo() override;
+	virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) override;
+	virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) override;
+	virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override;
 
-	virtual void process();
+	virtual void process() override;
 
 	// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
 	void _add_or_update_anchor(void *p_anchor);

+ 206 - 165
modules/arkit/arkit_interface.mm

@@ -42,7 +42,9 @@
 #include "arkit_session_delegate.h"
 
 // just a dirty workaround for now, declare these as globals. I'll probably encapsulate ARSession and associated logic into an mm object and change ARKitInterface to a normal cpp object that consumes it.
+API_AVAILABLE(ios(11.0))
 ARSession *ar_session;
+
 ARKitSessionDelegate *ar_delegate;
 NSTimeInterval last_timestamp;
 
@@ -55,22 +57,28 @@ void ARKitInterface::start_session() {
 	if (initialized) {
 		print_line("Starting ARKit session");
 
-		Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
-		ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
+		if (@available(iOS 11, *)) {
+			Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
+			ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
 
-		configuration.lightEstimationEnabled = light_estimation_is_enabled;
-		if (plane_detection_is_enabled) {
-			configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
-		} else {
-			configuration.planeDetection = 0;
-		}
+			configuration.lightEstimationEnabled = light_estimation_is_enabled;
+			if (plane_detection_is_enabled) {
+				if (@available(iOS 11.3, *)) {
+					configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
+				} else {
+					configuration.planeDetection = ARPlaneDetectionHorizontal;
+				}
+			} else {
+				configuration.planeDetection = 0;
+			}
 
-		// make sure our camera is on
-		if (feed.is_valid()) {
-			feed->set_active(true);
-		}
+			// make sure our camera is on
+			if (feed.is_valid()) {
+				feed->set_active(true);
+			}
 
-		[ar_session runWithConfiguration:configuration];
+			[ar_session runWithConfiguration:configuration];
+		}
 	}
 }
 
@@ -84,7 +92,9 @@ void ARKitInterface::stop_session() {
 			feed->set_active(false);
 		}
 
-		[ar_session pause];
+		if (@available(iOS 11.0, *)) {
+			[ar_session pause];
+		}
 	}
 }
 
@@ -92,12 +102,12 @@ void ARKitInterface::notification(int p_what) {
 	// TODO, this is not being called, need to find out why, possibly because this is not a node.
 	// in that case we need to find a way to get these notifications!
 	switch (p_what) {
-		case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
+		case DisplayServer::WINDOW_EVENT_FOCUS_IN: {
 			print_line("Focus in");
 
 			start_session();
 		}; break;
-		case MainLoop::NOTIFICATION_WM_FOCUS_OUT: {
+		case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {
 			print_line("Focus out");
 
 			stop_session();
@@ -162,37 +172,42 @@ int ARKitInterface::get_capabilities() const {
 }
 
 Array ARKitInterface::raycast(Vector2 p_screen_coord) {
-	Array arr;
-	Size2 screen_size = OS::get_singleton()->get_window_size();
-	CGPoint point;
-	point.x = p_screen_coord.x / screen_size.x;
-	point.y = p_screen_coord.y / screen_size.y;
-
-	///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
-	NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hittest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
-
-	for (ARHitTestResult *result in results) {
-		Transform transform;
-
-		matrix_float4x4 m44 = result.worldTransform;
-		transform.basis.elements[0].x = m44.columns[0][0];
-		transform.basis.elements[1].x = m44.columns[0][1];
-		transform.basis.elements[2].x = m44.columns[0][2];
-		transform.basis.elements[0].y = m44.columns[1][0];
-		transform.basis.elements[1].y = m44.columns[1][1];
-		transform.basis.elements[2].y = m44.columns[1][2];
-		transform.basis.elements[0].z = m44.columns[2][0];
-		transform.basis.elements[1].z = m44.columns[2][1];
-		transform.basis.elements[2].z = m44.columns[2][2];
-		transform.origin.x = m44.columns[3][0];
-		transform.origin.y = m44.columns[3][1];
-		transform.origin.z = m44.columns[3][2];
-
-		/* important, NOT scaled to world_scale !! */
-		arr.push_back(transform);
-	}
+	if (@available(iOS 11, *)) {
+		Array arr;
+		Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+		CGPoint point;
+		point.x = p_screen_coord.x / screen_size.x;
+		point.y = p_screen_coord.y / screen_size.y;
+
+		///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
+
+		NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
+
+		for (ARHitTestResult *result in results) {
+			Transform transform;
+
+			matrix_float4x4 m44 = result.worldTransform;
+			transform.basis.elements[0].x = m44.columns[0][0];
+			transform.basis.elements[1].x = m44.columns[0][1];
+			transform.basis.elements[2].x = m44.columns[0][2];
+			transform.basis.elements[0].y = m44.columns[1][0];
+			transform.basis.elements[1].y = m44.columns[1][1];
+			transform.basis.elements[2].y = m44.columns[1][2];
+			transform.basis.elements[0].z = m44.columns[2][0];
+			transform.basis.elements[1].z = m44.columns[2][1];
+			transform.basis.elements[2].z = m44.columns[2][2];
+			transform.origin.x = m44.columns[3][0];
+			transform.origin.y = m44.columns[3][1];
+			transform.origin.z = m44.columns[3][2];
+
+			/* important, NOT scaled to world_scale !! */
+			arr.push_back(transform);
+		}
 
-	return arr;
+		return arr;
+	} else {
+		return Array();
+	}
 }
 
 void ARKitInterface::_bind_methods() {
@@ -221,51 +236,55 @@ bool ARKitInterface::initialize() {
 	XRServer *xr_server = XRServer::get_singleton();
 	ERR_FAIL_NULL_V(xr_server, false);
 
-	if (!initialized) {
-		print_line("initializing ARKit");
+	if (@available(iOS 11, *)) {
+		if (!initialized) {
+			print_line("initializing ARKit");
 
-		// create our ar session and delegate
-		Class ARSessionClass = NSClassFromString(@"ARSession");
-		if (ARSessionClass == Nil) {
-			void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
-			if (arkit_handle) {
-				ARSessionClass = NSClassFromString(@"ARSession");
-			} else {
-				print_line("ARKit init failed");
-				return false;
+			// create our ar session and delegate
+			Class ARSessionClass = NSClassFromString(@"ARSession");
+			if (ARSessionClass == Nil) {
+				void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
+				if (arkit_handle) {
+					ARSessionClass = NSClassFromString(@"ARSession");
+				} else {
+					print_line("ARKit init failed");
+					return false;
+				}
 			}
-		}
-		ar_session = [ARSessionClass new];
-		ar_delegate = [ARKitSessionDelegate new];
-		ar_delegate.arkit_interface = this;
-		ar_session.delegate = ar_delegate;
+			ar_session = [ARSessionClass new];
+			ar_delegate = [ARKitSessionDelegate new];
+			ar_delegate.arkit_interface = this;
+			ar_session.delegate = ar_delegate;
 
-		// reset our transform
-		transform = Transform();
+			// reset our transform
+			transform = Transform();
 
-		// make this our primary interface
-		xr_server->set_primary_interface(this);
+			// make this our primary interface
+			xr_server->set_primary_interface(this);
 
-		// make sure we have our feed setup
-		if (feed.is_null()) {
-			feed.instance();
-			feed->set_name("ARKit");
+			// make sure we have our feed setup
+			if (feed.is_null()) {
+				feed.instance();
+				feed->set_name("ARKit");
 
-			CameraServer *cs = CameraServer::get_singleton();
-			if (cs != NULL) {
-				cs->add_feed(feed);
+				CameraServer *cs = CameraServer::get_singleton();
+				if (cs != NULL) {
+					cs->add_feed(feed);
+				}
 			}
-		}
-		feed->set_active(true);
+			feed->set_active(true);
 
-		// yeah!
-		initialized = true;
+			// yeah!
+			initialized = true;
 
-		// Start our session...
-		start_session();
-	}
+			// Start our session...
+			start_session();
+		}
 
-	return true;
+		return true;
+	} else {
+		return false;
+	}
 }
 
 void ARKitInterface::uninitialize() {
@@ -286,9 +305,12 @@ void ARKitInterface::uninitialize() {
 
 		remove_all_anchors();
 
-		[ar_session release];
+		if (@available(iOS 11.0, *)) {
+			[ar_session release];
+			ar_session = NULL;
+		}
 		[ar_delegate release];
-		ar_session = NULL;
+
 		ar_delegate = NULL;
 		initialized = false;
 		session_was_started = false;
@@ -296,15 +318,15 @@ void ARKitInterface::uninitialize() {
 }
 
 Size2 ARKitInterface::get_render_targetsize() {
-	_THREAD_SAFE_METHOD_
+	// _THREAD_SAFE_METHOD_
 
-	Size2 target_size = OS::get_singleton()->get_window_size();
+	Size2 target_size = DisplayServer::get_singleton()->screen_get_size();
 
 	return target_size;
 }
 
 Transform ARKitInterface::get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) {
-	_THREAD_SAFE_METHOD_
+	// _THREAD_SAFE_METHOD_
 
 	Transform transform_for_eye;
 
@@ -336,7 +358,7 @@ CameraMatrix ARKitInterface::get_projection_for_eye(XRInterface::Eyes p_eye, rea
 }
 
 void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) {
-	_THREAD_SAFE_METHOD_
+	// _THREAD_SAFE_METHOD_
 
 	// We must have a valid render target
 	ERR_FAIL_COND(!p_render_target.is_valid());
@@ -345,15 +367,15 @@ void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target
 	ERR_FAIL_COND(p_screen_rect == Rect2());
 
 	// get the size of our screen
-	Rect2 screen_rect = p_screen_rect;
+	//	Rect2 screen_rect = p_screen_rect;
 
 	//		screen_rect.position.x += screen_rect.size.x;
 	//		screen_rect.size.x = -screen_rect.size.x;
 	//		screen_rect.position.y += screen_rect.size.y;
 	//		screen_rect.size.y = -screen_rect.size.y;
 
-	VSG::rasterizer->set_current_render_target(RID());
-	VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
+	//	VSG::rasterizer->set_current_render_target(RID());
+	//	VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
 }
 
 XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_uuid) {
@@ -432,7 +454,7 @@ void ARKitInterface::remove_all_anchors() {
 }
 
 void ARKitInterface::process() {
-	_THREAD_SAFE_METHOD_
+	// _THREAD_SAFE_METHOD_
 
 	if (@available(iOS 11.0, *)) {
 		if (initialized) {
@@ -443,8 +465,16 @@ void ARKitInterface::process() {
 				last_timestamp = current_frame.timestamp;
 
 				// get some info about our screen and orientation
-				Size2 screen_size = OS::get_singleton()->get_window_size();
-				UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+				Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+				UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+				if (@available(iOS 13, *)) {
+					orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+				} else {
+					orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+				}
 
 				// Grab our camera image for our backbuffer
 				CVPixelBufferRef pixelBuffer = current_frame.capturedImage;
@@ -475,27 +505,27 @@ void ARKitInterface::process() {
 
 						{
 							// do Y
-							int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
-							int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
-							int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+							size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+							size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+							size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
 
 							if ((image_width[0] != new_width) || (image_height[0] != new_height)) {
 								printf("- Camera padding l:%lu r:%lu t:%lu b:%lu\n", extraLeft, extraRight, extraTop, extraBottom);
-								printf("- Camera Y plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+								printf("- Camera Y plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
 
 								image_width[0] = new_width;
 								image_height[0] = new_height;
 								img_data[0].resize(new_width * new_height);
 							}
 
-							uint8_t *w = img_data[0].write();
+							uint8_t *w = img_data[0].ptrw();
 							if (new_width == bytes_per_row) {
-								memcpy(w.ptr(), dataY, new_width * new_height);
+								memcpy(w, dataY, new_width * new_height);
 							} else {
-								int offset_a = 0;
-								int offset_b = extraLeft + (extraTop * bytes_per_row);
-								for (int r = 0; r < new_height; r++) {
-									memcpy(w.ptr() + offset_a, dataY + offset_b, new_width);
+								size_t offset_a = 0;
+								size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+								for (size_t r = 0; r < new_height; r++) {
+									memcpy(w + offset_a, dataY + offset_b, new_width);
 									offset_a += new_width;
 									offset_b += bytes_per_row;
 								}
@@ -507,26 +537,26 @@ void ARKitInterface::process() {
 
 						{
 							// do CbCr
-							int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
-							int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
-							int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+							size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+							size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+							size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
 
 							if ((image_width[1] != new_width) || (image_height[1] != new_height)) {
-								printf("- Camera CbCr plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+								printf("- Camera CbCr plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
 
 								image_width[1] = new_width;
 								image_height[1] = new_height;
 								img_data[1].resize(2 * new_width * new_height);
 							}
 
-							uint8_t *w = img_data[1].write();
+							uint8_t *w = img_data[1].ptrw();
 							if ((2 * new_width) == bytes_per_row) {
-								memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+								memcpy(w, dataCbCr, 2 * new_width * new_height);
 							} else {
-								int offset_a = 0;
-								int offset_b = extraLeft + (extraTop * bytes_per_row);
-								for (int r = 0; r < new_height; r++) {
-									memcpy(w.ptr() + offset_a, dataCbCr + offset_b, 2 * new_width);
+								size_t offset_a = 0;
+								size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+								for (size_t r = 0; r < new_height; r++) {
+									memcpy(w + offset_a, dataCbCr + offset_b, 2 * new_width);
 									offset_a += 2 * new_width;
 									offset_b += bytes_per_row;
 								}
@@ -658,69 +688,78 @@ void ARKitInterface::process() {
 }
 
 void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
-	_THREAD_SAFE_METHOD_
-
-	ARAnchor *anchor = (ARAnchor *)p_anchor;
-
-	unsigned char uuid[16];
-	[anchor.identifier getUUIDBytes:uuid];
-
-	XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
-	if (tracker != NULL) {
-		// lets update our mesh! (using Arjens code as is for now)
-		// we should also probably limit how often we do this...
+	// _THREAD_SAFE_METHOD_
 
-		// can we safely cast this?
-		ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
-
-		if (planeAnchor.geometry.triangleCount > 0) {
-			Ref<SurfaceTool> surftool;
-			surftool.instance();
-			surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+	if (@available(iOS 11.0, *)) {
+		ARAnchor *anchor = (ARAnchor *)p_anchor;
+
+		unsigned char uuid[16];
+		[anchor.identifier getUUIDBytes:uuid];
+
+		XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
+		if (tracker != NULL) {
+			// lets update our mesh! (using Arjens code as is for now)
+			// we should also probably limit how often we do this...
+
+			// can we safely cast this?
+			ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
+
+			if (@available(iOS 11.3, *)) {
+				if (planeAnchor.geometry.triangleCount > 0) {
+					Ref<SurfaceTool> surftool;
+					surftool.instance();
+					surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+					for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
+						int16_t index = planeAnchor.geometry.triangleIndices[j];
+						simd_float3 vrtx = planeAnchor.geometry.vertices[index];
+						simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
+						surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
+						surftool->add_color(Color(0.8, 0.8, 0.8));
+						surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+					}
 
-			for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
-				int16_t index = planeAnchor.geometry.triangleIndices[j];
-				simd_float3 vrtx = planeAnchor.geometry.vertices[index];
-				simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
-				surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
-				surftool->add_color(Color(0.8, 0.8, 0.8));
-				surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+					surftool->generate_normals();
+					tracker->set_mesh(surftool->commit());
+				} else {
+					Ref<Mesh> nomesh;
+					tracker->set_mesh(nomesh);
+				}
+			} else {
+				Ref<Mesh> nomesh;
+				tracker->set_mesh(nomesh);
 			}
 
-			surftool->generate_normals();
-			tracker->set_mesh(surftool->commit());
-		} else {
-			Ref<Mesh> nomesh;
-			tracker->set_mesh(nomesh);
+			// Note, this also contains a scale factor which gives us an idea of the size of the anchor
+			// We may extract that in our XRAnchor class
+			Basis b;
+			matrix_float4x4 m44 = anchor.transform;
+			b.elements[0].x = m44.columns[0][0];
+			b.elements[1].x = m44.columns[0][1];
+			b.elements[2].x = m44.columns[0][2];
+			b.elements[0].y = m44.columns[1][0];
+			b.elements[1].y = m44.columns[1][1];
+			b.elements[2].y = m44.columns[1][2];
+			b.elements[0].z = m44.columns[2][0];
+			b.elements[1].z = m44.columns[2][1];
+			b.elements[2].z = m44.columns[2][2];
+			tracker->set_orientation(b);
+			tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
 		}
-
-		// Note, this also contains a scale factor which gives us an idea of the size of the anchor
-		// We may extract that in our XRAnchor class
-		Basis b;
-		matrix_float4x4 m44 = anchor.transform;
-		b.elements[0].x = m44.columns[0][0];
-		b.elements[1].x = m44.columns[0][1];
-		b.elements[2].x = m44.columns[0][2];
-		b.elements[0].y = m44.columns[1][0];
-		b.elements[1].y = m44.columns[1][1];
-		b.elements[2].y = m44.columns[1][2];
-		b.elements[0].z = m44.columns[2][0];
-		b.elements[1].z = m44.columns[2][1];
-		b.elements[2].z = m44.columns[2][2];
-		tracker->set_orientation(b);
-		tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
 	}
 }
 
 void ARKitInterface::_remove_anchor(void *p_anchor) {
-	_THREAD_SAFE_METHOD_
+	// _THREAD_SAFE_METHOD_
 
-	ARAnchor *anchor = (ARAnchor *)p_anchor;
+	if (@available(iOS 11.0, *)) {
+		ARAnchor *anchor = (ARAnchor *)p_anchor;
 
-	unsigned char uuid[16];
-	[anchor.identifier getUUIDBytes:uuid];
+		unsigned char uuid[16];
+		[anchor.identifier getUUIDBytes:uuid];
 
-	remove_anchor_for_uuid(uuid);
+		remove_anchor_for_uuid(uuid);
+	}
 }
 
 ARKitInterface::ARKitInterface() {
@@ -728,7 +767,9 @@ ARKitInterface::ARKitInterface() {
 	session_was_started = false;
 	plane_detection_is_enabled = false;
 	light_estimation_is_enabled = false;
-	ar_session = NULL;
+	if (@available(iOS 11.0, *)) {
+		ar_session = NULL;
+	}
 	z_near = 0.01;
 	z_far = 1000.0;
 	projection.set_perspective(60.0, 1.0, z_near, z_far, false);

+ 3 - 3
modules/arkit/arkit_session_delegate.h

@@ -42,9 +42,9 @@ class ARKitInterface;
 
 @property(nonatomic) ARKitInterface *arkit_interface;
 
-- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors;
+- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
 @end
 
 #endif /* !ARKIT_SESSION_DELEGATE_H */

+ 57 - 36
modules/camera/camera_ios.mm

@@ -158,25 +158,31 @@
 	} else if (dataCbCr == NULL) {
 		print_line("Couldn't access CbCr pixel buffer data");
 	} else {
-		UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+		UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+		if (@available(iOS 13, *)) {
+			orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+		} else {
+			orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+		}
+
 		Ref<Image> img[2];
 
 		{
 			// do Y
-			int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
-			int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
-			int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+			size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+			size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
 
 			if ((width[0] != new_width) || (height[0] != new_height)) {
-				// printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
 				width[0] = new_width;
 				height[0] = new_height;
 				img_data[0].resize(new_width * new_height);
 			}
 
 			uint8_t *w = img_data[0].ptrw();
-			memcpy(w.ptr(), dataY, new_width * new_height);
+			memcpy(w, dataY, new_width * new_height);
 
 			img[0].instance();
 			img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
@@ -184,20 +190,17 @@
 
 		{
 			// do CbCr
-			int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
-			int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
-			int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
+			size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+			size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
 
 			if ((width[1] != new_width) || (height[1] != new_height)) {
-				// printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
 				width[1] = new_width;
 				height[1] = new_height;
 				img_data[1].resize(2 * new_width * new_height);
 			}
 
 			uint8_t *w = img_data[1].ptrw();
-			memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+			memcpy(w, dataCbCr, 2 * new_width * new_height);
 
 			///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
 			img[1].instance();
@@ -359,41 +362,59 @@ void CameraIOS::update_feeds() {
 	// this way of doing things is deprecated but still works,
 	// rewrite to using AVCaptureDeviceDiscoverySession
 
-	AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+	NSMutableArray *deviceTypes = [NSMutableArray array];
 
-	// remove devices that are gone..
-	for (int i = feeds.size() - 1; i >= 0; i--) {
-		Ref<CameraFeedIOS> feed(feeds[i]);
+	if (@available(iOS 10, *)) {
+		[deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
+		[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera];
 
-		if (feed.is_null()) {
-			// feed not managed by us
-		} else if (![session.devices containsObject:feed->get_device()]) {
-			// remove it from our array, this will also destroy it ;)
-			remove_feed(feed);
-		};
-	};
+		if (@available(iOS 10.2, *)) {
+			[deviceTypes addObject:AVCaptureDeviceTypeBuiltInDualCamera];
+		}
 
-	// add new devices..
-	for (AVCaptureDevice *device in session.devices) {
-		bool found = false;
+		if (@available(iOS 11.1, *)) {
+			[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTrueDepthCamera];
+		}
+
+		AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
+				discoverySessionWithDeviceTypes:deviceTypes
+									  mediaType:AVMediaTypeVideo
+									   position:AVCaptureDevicePositionUnspecified];
 
-		for (int i = 0; i < feeds.size() && !found; i++) {
+		// remove devices that are gone..
+		for (int i = feeds.size() - 1; i >= 0; i--) {
 			Ref<CameraFeedIOS> feed(feeds[i]);
 
 			if (feed.is_null()) {
 				// feed not managed by us
-			} else if (feed->get_device() == device) {
-				found = true;
+			} else if (![session.devices containsObject:feed->get_device()]) {
+				// remove it from our array, this will also destroy it ;)
+				remove_feed(feed);
 			};
 		};
 
-		if (!found) {
-			Ref<CameraFeedIOS> newfeed;
-			newfeed.instance();
-			newfeed->set_device(device);
-			add_feed(newfeed);
+		// add new devices..
+		for (AVCaptureDevice *device in session.devices) {
+			bool found = false;
+
+			for (int i = 0; i < feeds.size() && !found; i++) {
+				Ref<CameraFeedIOS> feed(feeds[i]);
+
+				if (feed.is_null()) {
+					// feed not managed by us
+				} else if (feed->get_device() == device) {
+					found = true;
+				};
+			};
+
+			if (!found) {
+				Ref<CameraFeedIOS> newfeed;
+				newfeed.instance();
+				newfeed->set_device(device);
+				add_feed(newfeed);
+			};
 		};
-	};
+	}
 };
 
 CameraIOS::CameraIOS() {

+ 7 - 4
platform/iphone/SCsub

@@ -3,10 +3,8 @@
 Import("env")
 
 iphone_lib = [
-    "godot_iphone.cpp",
-    "os_iphone.cpp",
-    "semaphore_iphone.cpp",
-    "gl_view.mm",
+    "godot_iphone.mm",
+    "os_iphone.mm",
     "main.m",
     "app_delegate.mm",
     "view_controller.mm",
@@ -15,6 +13,11 @@ iphone_lib = [
     "icloud.mm",
     "ios.mm",
     "vulkan_context_iphone.mm",
+    "display_server_iphone.mm",
+    "joypad_iphone.mm",
+    "godot_view.mm",
+    "display_layer.mm",
+    "godot_view_renderer.mm",
 ]
 
 env_ios = env.Clone()

+ 5 - 14
platform/iphone/app_delegate.h

@@ -28,29 +28,20 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
-#import "view_controller.h"
 #import <UIKit/UIKit.h>
 
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
 
 // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
 // so it can't be done with compilation time branching.
 //#if defined(OPENGL_ENABLED)
 //@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
 //#endif
-#if defined(VULKAN_ENABLED)
-@interface AppDelegate : NSObject <UIApplicationDelegate> {
-#endif
-	//@property (strong, nonatomic) UIWindow *window;
-	ViewController *view_controller;
-	bool is_focus_out;
-};
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject <UIApplicationDelegate>
+//#endif
 
 @property(strong, nonatomic) UIWindow *window;
-
-+ (ViewController *)getViewController;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
 
 @end

+ 34 - 625
platform/iphone/app_delegate.mm

@@ -29,644 +29,60 @@
 /*************************************************************************/
 
 #import "app_delegate.h"
-
 #include "core/project_settings.h"
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
+#import "godot_view.h"
 #include "main/main.h"
 #include "os_iphone.h"
+#import "view_controller.h"
 
-#import "GameController/GameController.h"
 #import <AudioToolbox/AudioServices.h>
 
-#define kFilteringFactor 0.1
 #define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
-	NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-
-	if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
-		return ERR_CANT_OPEN;
-
-	printf("opening url %ls\n", p_uri.c_str());
-	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
-	[url release];
-	return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
-	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
-	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
 
 extern int gargc;
 extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
 extern void iphone_finish();
 
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
 
 static ViewController *mainViewController = nil;
-+ (ViewController *)getViewController {
-	return mainViewController;
-}
-
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
-	bool have_player_1 = false;
-	bool have_player_2 = false;
-	bool have_player_3 = false;
-	bool have_player_4 = false;
-
-	if (ios_joysticks == nil) {
-		NSArray *keys = [ios_joysticks allKeys];
-		for (NSNumber *key in keys) {
-			GCController *controller = [ios_joysticks objectForKey:key];
-			if (controller.playerIndex == GCControllerPlayerIndex1) {
-				have_player_1 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
-				have_player_2 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
-				have_player_3 = true;
-			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
-				have_player_4 = true;
-			};
-		};
-	};
-
-	if (!have_player_1) {
-		return GCControllerPlayerIndex1;
-	} else if (!have_player_2) {
-		return GCControllerPlayerIndex2;
-	} else if (!have_player_3) {
-		return GCControllerPlayerIndex3;
-	} else if (!have_player_4) {
-		return GCControllerPlayerIndex4;
-	} else {
-		return GCControllerPlayerIndexUnset;
-	};
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
-	// get a new id for our controller
-	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
-	if (joy_id != -1) {
-		// assign our player index
-		if (controller.playerIndex == GCControllerPlayerIndexUnset) {
-			controller.playerIndex = [delegate getFreePlayerIndex];
-		};
-
-		// tell Godot about our new controller
-		OSIPhone::get_singleton()->joy_connection_changed(
-				joy_id, true, [controller.vendorName UTF8String]);
-
-		// add it to our dictionary, this will retain our controllers
-		[ios_joysticks setObject:controller
-						  forKey:[NSNumber numberWithInt:joy_id]];
-
-		// set our input handler
-		[delegate setControllerInputHandler:controller];
-	} else {
-		printf("Couldn't retrieve new joy id\n");
-	};
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
-	if (!*is_focus_out) {
-		*is_focus_out = true;
-		if (OS::get_singleton()->get_main_loop())
-			OS::get_singleton()->get_main_loop()->notification(
-					MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
-		[view_controller.view stopAnimation];
-		if (OS::get_singleton()->native_video_is_playing()) {
-			OSIPhone::get_singleton()->native_video_focus_out();
-		}
 
-		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
-		if (audio)
-			audio->stop();
-	}
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
-	if (*is_focus_out) {
-		*is_focus_out = false;
-		if (OS::get_singleton()->get_main_loop())
-			OS::get_singleton()->get_main_loop()->notification(
-					MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
-		[view_controller.view startAnimation];
-		if (OSIPhone::get_singleton()->native_video_is_playing()) {
-			OSIPhone::get_singleton()->native_video_unpause();
-		}
-
-		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
-		if (audio)
-			audio->start();
-	}
++ (ViewController *)viewController {
+	return mainViewController;
 }
 
-- (void)controllerWasConnected:(NSNotification *)notification {
-	// create our dictionary if we don't have one yet
-	if (ios_joysticks == nil) {
-		ios_joysticks = [[NSMutableDictionary alloc] init];
-	};
-
-	// get our controller
-	GCController *controller = (GCController *)notification.object;
-	if (controller == nil) {
-		printf("Couldn't retrieve new controller\n");
-	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
-		printf("Controller is already registered\n");
-	} else if (frame_count > 1) {
-		_ios_add_joystick(controller, self);
-	} else {
-		if (pending_ios_joysticks == nil)
-			pending_ios_joysticks = [[NSMutableArray alloc] init];
-		[pending_ios_joysticks addObject:controller];
-	};
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
-	if (ios_joysticks != nil) {
-		// find our joystick, there should be only one in our dictionary
-		GCController *controller = (GCController *)notification.object;
-		NSArray *keys = [ios_joysticks allKeysForObject:controller];
-		for (NSNumber *key in keys) {
-			// tell Godot this joystick is no longer there
-			int joy_id = [key intValue];
-			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
-			// and remove it from our dictionary
-			[ios_joysticks removeObjectForKey:key];
-		};
-	};
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
-	if (ios_joysticks != nil) {
-		// find our joystick, there should be only one in our dictionary
-		NSArray *keys = [ios_joysticks allKeysForObject:controller];
-		for (NSNumber *key in keys) {
-			int joy_id = [key intValue];
-			return joy_id;
-		};
-	};
-
-	return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
-	// Hook in the callback handler for the correct gamepad profile.
-	// This is a bit of a weird design choice on Apples part.
-	// You need to select the most capable gamepad profile for the
-	// gamepad attached.
-	if (controller.extendedGamepad != nil) {
-		// The extended gamepad profile has all the input you could possibly find on
-		// a gamepad but will only be active if your gamepad actually has all of
-		// these...
-		controller.extendedGamepad.valueChangedHandler = ^(
-				GCExtendedGamepad *gamepad, GCControllerElement *element) {
-			int joy_id = [self getJoyIdForController:controller];
-
-			if (element == gamepad.buttonA) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
-						gamepad.buttonA.isPressed);
-			} else if (element == gamepad.buttonB) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
-						gamepad.buttonB.isPressed);
-			} else if (element == gamepad.buttonX) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
-						gamepad.buttonX.isPressed);
-			} else if (element == gamepad.buttonY) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
-						gamepad.buttonY.isPressed);
-			} else if (element == gamepad.leftShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
-						gamepad.leftShoulder.isPressed);
-			} else if (element == gamepad.rightShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
-						gamepad.rightShoulder.isPressed);
-			} else if (element == gamepad.dpad) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
-						gamepad.dpad.up.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
-						gamepad.dpad.down.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
-						gamepad.dpad.left.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
-						gamepad.dpad.right.isPressed);
-			};
-
-			InputDefault::JoyAxis jx;
-			jx.min = -1;
-			if (element == gamepad.leftThumbstick) {
-				jx.value = gamepad.leftThumbstick.xAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
-				jx.value = -gamepad.leftThumbstick.yAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
-			} else if (element == gamepad.rightThumbstick) {
-				jx.value = gamepad.rightThumbstick.xAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
-				jx.value = -gamepad.rightThumbstick.yAxis.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
-			} else if (element == gamepad.leftTrigger) {
-				jx.value = gamepad.leftTrigger.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
-			} else if (element == gamepad.rightTrigger) {
-				jx.value = gamepad.rightTrigger.value;
-				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
-			};
-		};
-	} else if (controller.gamepad != nil) {
-		// gamepad is the standard profile with 4 buttons, shoulder buttons and a
-		// D-pad
-		controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
-				GCControllerElement *element) {
-			int joy_id = [self getJoyIdForController:controller];
-
-			if (element == gamepad.buttonA) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
-						gamepad.buttonA.isPressed);
-			} else if (element == gamepad.buttonB) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
-						gamepad.buttonB.isPressed);
-			} else if (element == gamepad.buttonX) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
-						gamepad.buttonX.isPressed);
-			} else if (element == gamepad.buttonY) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
-						gamepad.buttonY.isPressed);
-			} else if (element == gamepad.leftShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
-						gamepad.leftShoulder.isPressed);
-			} else if (element == gamepad.rightShoulder) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
-						gamepad.rightShoulder.isPressed);
-			} else if (element == gamepad.dpad) {
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
-						gamepad.dpad.up.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
-						gamepad.dpad.down.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
-						gamepad.dpad.left.isPressed);
-				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
-						gamepad.dpad.right.isPressed);
-			};
-		};
-#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
-		// while we are setting that as the minimum, seems our
-		// build environment doesn't like it
-	} else if (controller.microGamepad != nil) {
-		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
-		controller.microGamepad.valueChangedHandler =
-				^(GCMicroGamepad *gamepad, GCControllerElement *element) {
-					int joy_id = [self getJoyIdForController:controller];
-
-					if (element == gamepad.buttonA) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
-								gamepad.buttonA.isPressed);
-					} else if (element == gamepad.buttonX) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
-								gamepad.buttonX.isPressed);
-					} else if (element == gamepad.dpad) {
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
-								gamepad.dpad.up.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
-								gamepad.dpad.down.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
-								gamepad.dpad.left.isPressed);
-						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
-								gamepad.dpad.right.isPressed);
-					};
-				};
-#endif
-	};
-
-	///@TODO need to add support for controller.motion which gives us access to
-	/// the orientation of the device (if supported)
-
-	///@TODO need to add support for controllerPausedHandler which should be a
-	/// toggle
-};
-
-- (void)initGameControllers {
-	// get told when controllers connect, this will be called right away for
-	// already connected controllers
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(controllerWasConnected:)
-				   name:GCControllerDidConnectNotification
-				 object:nil];
-
-	// get told when controllers disconnect
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(controllerWasDisconnected:)
-				   name:GCControllerDidDisconnectNotification
-				 object:nil];
-};
-
-- (void)deinitGameControllers {
-	[[NSNotificationCenter defaultCenter]
-			removeObserver:self
-					  name:GCControllerDidConnectNotification
-					object:nil];
-	[[NSNotificationCenter defaultCenter]
-			removeObserver:self
-					  name:GCControllerDidDisconnectNotification
-					object:nil];
-
-	if (ios_joysticks != nil) {
-		[ios_joysticks dealloc];
-		ios_joysticks = nil;
-	};
-
-	if (pending_ios_joysticks != nil) {
-		[pending_ios_joysticks dealloc];
-		pending_ios_joysticks = nil;
-	};
-};
-
-OS::VideoMode _get_video_mode() {
-	int backingWidth;
-	int backingHeight;
-#if defined(OPENGL_ENABLED)
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
-			GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
-			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-#endif
-
-	OS::VideoMode vm;
-	vm.fullscreen = true;
-	vm.width = backingWidth;
-	vm.height = backingHeight;
-	vm.resizable = false;
-	return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(UIView *)view;
-{
-	switch (frame_count) {
-		case 0: {
-			OS::get_singleton()->set_video_mode(_get_video_mode());
-
-			if (!OS::get_singleton()) {
-				exit(0);
-			};
-			++frame_count;
-
-			NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
-			OSIPhone::get_singleton()->set_locale(
-					String::utf8([locale_code UTF8String]));
-
-			NSString *uuid;
-			if ([[UIDevice currentDevice]
-						respondsToSelector:@selector(identifierForVendor)]) {
-				uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
-			} else {
-				// before iOS 6, so just generate an identifier and store it
-				uuid = [[NSUserDefaults standardUserDefaults]
-						objectForKey:@"identiferForVendor"];
-				if (!uuid) {
-					CFUUIDRef cfuuid = CFUUIDCreate(NULL);
-					uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
-					CFRelease(cfuuid);
-					[[NSUserDefaults standardUserDefaults]
-							setObject:uuid
-							   forKey:@"identifierForVendor"];
-				}
-			}
-
-			OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-
-		}; break;
-
-		case 1: {
-			Main::setup2();
-			++frame_count;
-
-			if (pending_ios_joysticks != nil) {
-				for (GCController *controller in pending_ios_joysticks) {
-					_ios_add_joystick(controller, self);
-				}
-				[pending_ios_joysticks dealloc];
-				pending_ios_joysticks = nil;
-			}
-
-			// this might be necessary before here
-			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
-			for (NSString *key in dict) {
-				NSObject *value = [dict objectForKey:key];
-				String ukey = String::utf8([key UTF8String]);
-
-				// we need a NSObject to Variant conversor
-
-				if ([value isKindOfClass:[NSString class]]) {
-					NSString *str = (NSString *)value;
-					String uval = String::utf8([str UTF8String]);
-
-					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
-				} else if ([value isKindOfClass:[NSNumber class]]) {
-					NSNumber *n = (NSNumber *)value;
-					double dval = [n doubleValue];
-
-					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
-				};
-				// do stuff
-			}
-
-		}; break;
-
-		case 2: {
-			Main::start();
-			++frame_count;
-
-		}; break; // no fallthrough
-
-		default: {
-			if (OSIPhone::get_singleton()) {
-				// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
-				// accel[2]);
-				if (motionInitialised) {
-					// Just using polling approach for now, we can set this up so it sends
-					// data to us in intervals, might be better. See Apple reference pages
-					// for more details:
-					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
-					// Apple splits our accelerometer date into a gravity and user movement
-					// component. We add them back together
-					CMAcceleration gravity = motionManager.deviceMotion.gravity;
-					CMAcceleration acceleration =
-							motionManager.deviceMotion.userAcceleration;
-
-					///@TODO We don't seem to be getting data here, is my device broken or
-					/// is this code incorrect?
-					CMMagneticField magnetic =
-							motionManager.deviceMotion.magneticField.field;
-
-					///@TODO we can access rotationRate as a CMRotationRate variable
-					///(processed date) or CMGyroData (raw data), have to see what works
-					/// best
-					CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
-					// Adjust for screen orientation.
-					// [[UIDevice currentDevice] orientation] changes even if we've fixed
-					// our orientation which is not a good thing when you're trying to get
-					// your user to move the screen in all directions and want consistent
-					// output
-
-					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
-					/// is a bit of a hack. Godot obviously knows the orientation so maybe
-					/// we
-					// can use that instead? (note that left and right seem swapped)
-
-					switch ([[UIApplication sharedApplication] statusBarOrientation]) {
-						case UIDeviceOrientationLandscapeLeft: {
-							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									-(acceleration.y + gravity.y), (acceleration.x + gravity.x),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									-magnetic.y, magnetic.x, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
-									rotation.z);
-						}; break;
-						case UIDeviceOrientationLandscapeRight: {
-							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									(acceleration.y + gravity.y), -(acceleration.x + gravity.x),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									magnetic.y, -magnetic.x, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
-									rotation.z);
-						}; break;
-						case UIDeviceOrientationPortraitUpsideDown: {
-							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									-(acceleration.x + gravity.x), (acceleration.y + gravity.y),
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(
-									-magnetic.x, magnetic.y, magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
-									rotation.z);
-						}; break;
-						default: { // assume portrait
-							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
-									gravity.z);
-							OSIPhone::get_singleton()->update_accelerometer(
-									acceleration.x + gravity.x, acceleration.y + gravity.y,
-									acceleration.z + gravity.z);
-							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
-									magnetic.z);
-							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
-									rotation.z);
-						}; break;
-					};
-				}
-
-				bool quit_request = OSIPhone::get_singleton()->iterate();
-			};
-
-		}; break;
-	};
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
-	if (OS::get_singleton()->get_main_loop()) {
-		OS::get_singleton()->get_main_loop()->notification(
-				MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
-	}
-};
-
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-	CGRect rect = [[UIScreen mainScreen] bounds];
+	// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
+	// TODO: logo screen is not displayed while shaders are compiling
+	// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
 
-	is_focus_out = false;
-
-	[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
-	// disable idle timer
-	// application.idleTimerDisabled = YES;
+	CGRect windowBounds = [[UIScreen mainScreen] bounds];
 
 	// Create a full-screen window
-	window = [[UIWindow alloc] initWithFrame:rect];
-
-	OS::VideoMode vm = _get_video_mode();
+	self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
 
-	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
-			NSUserDomainMask, YES);
+	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 	NSString *documentsDirectory = [paths objectAtIndex:0];
 
-	int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+	int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+
 	if (err != 0) {
 		// bail, things did not go very well for us, should probably output a message on screen with our error code...
 		exit(0);
-		return FALSE;
+		return NO;
 	};
 
-#if defined(OPENGL_ENABLED)
-	// WARNING: We must *always* create the GLView after we have constructed the
-	// OS with iphone_main. This allows the GLView to access project settings so
-	// it can properly initialize the OpenGL context
-	GLView *glView = [[GLView alloc] initWithFrame:rect];
-	glView.delegate = self;
-
-	view_controller = [[ViewController alloc] init];
-	view_controller.view = glView;
-
-	_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
-	glView.useCADisplayLink =
-			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
-	printf("cadisaplylink: %d", glView.useCADisplayLink);
-	glView.animationInterval = 1.0 / kRenderingFrequency;
-	[glView startAnimation];
-#endif
-
-#if defined(VULKAN_ENABLED)
-	view_controller = [[ViewController alloc] init];
-#endif
+	ViewController *viewController = [[ViewController alloc] init];
+	viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+	viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
 
-	window.rootViewController = view_controller;
+	self.window.rootViewController = viewController;
 
 	// Show the window
-	[window makeKeyAndVisible];
-
-	// Configure and start accelerometer
-	if (!motionInitialised) {
-		motionManager = [[CMMotionManager alloc] init];
-		if (motionManager.deviceMotionAvailable) {
-			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
-			[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
-								   CMAttitudeReferenceFrameXMagneticNorthZVertical];
-			motionInitialised = YES;
-		};
-	};
-
-	[self initGameControllers];
+	[self.window makeKeyAndVisible];
 
 	[[NSNotificationCenter defaultCenter]
 			addObserver:self
@@ -674,40 +90,33 @@ static int frame_count = 0;
 				   name:AVAudioSessionInterruptionNotification
 				 object:[AVAudioSession sharedInstance]];
 
-	// OSIPhone::screen_width = rect.size.width - rect.origin.x;
-	// OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
-	mainViewController = view_controller;
+	mainViewController = viewController;
 
 	// prevent to stop music in another background app
 	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
 
-	return TRUE;
+	return YES;
 };
 
 - (void)onAudioInterruption:(NSNotification *)notification {
 	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
 		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
 			NSLog(@"Audio interruption began");
-			on_focus_out(view_controller, &is_focus_out);
+			OSIPhone::get_singleton()->on_focus_out();
 		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
 			NSLog(@"Audio interruption ended");
-			on_focus_in(view_controller, &is_focus_out);
+			OSIPhone::get_singleton()->on_focus_in();
 		}
 	}
 };
 
-- (void)applicationWillTerminate:(UIApplication *)application {
-	[self deinitGameControllers];
-
-	if (motionInitialised) {
-		///@TODO is this the right place to clean this up?
-		[motionManager stopDeviceMotionUpdates];
-		[motionManager release];
-		motionManager = nil;
-		motionInitialised = NO;
-	};
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+	if (OS::get_singleton()->get_main_loop()) {
+		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+	}
+};
 
+- (void)applicationWillTerminate:(UIApplication *)application {
 	iphone_finish();
 };
 
@@ -722,15 +131,15 @@ static int frame_count = 0;
 // notification panel by swiping from the upper part of the screen.
 
 - (void)applicationWillResignActive:(UIApplication *)application {
-	on_focus_out(view_controller, &is_focus_out);
+	OSIPhone::get_singleton()->on_focus_out();
 }
 
 - (void)applicationDidBecomeActive:(UIApplication *)application {
-	on_focus_in(view_controller, &is_focus_out);
+	OSIPhone::get_singleton()->on_focus_in();
 }
 
 - (void)dealloc {
-	[window release];
+	self.window = nil;
 	[super dealloc];
 }
 

+ 13 - 11
platform/iphone/detect.py

@@ -120,18 +120,18 @@ def configure(env):
             CCFLAGS=(
                 "-arch "
                 + arch_flag
-                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"
             ).split()
         )
     elif env["arch"] == "arm":
         detect_darwin_sdk_path("iphone", env)
         env.Append(
-            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
+            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split()
         )
     elif env["arch"] == "arm64":
         detect_darwin_sdk_path("iphone", env)
         env.Append(
-            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
+            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0 -isysroot $IPHONESDK".split()
         )
         env.Append(CPPDEFINES=["NEED_LONG_INT"])
         env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@@ -143,6 +143,9 @@ def configure(env):
         else:
             env.Append(CCFLAGS=["-fno-exceptions"])
 
+    # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+    env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
     ## Link flags
 
     if env["arch"] == "x86" or env["arch"] == "x86_64":
@@ -151,7 +154,7 @@ def configure(env):
             LINKFLAGS=[
                 "-arch",
                 arch_flag,
-                "-mios-simulator-version-min=10.0",
+                "-mios-simulator-version-min=13.0",
                 "-isysroot",
                 "$IPHONESDK",
                 "-Xlinker",
@@ -162,9 +165,9 @@ def configure(env):
             ]
         )
     elif env["arch"] == "arm":
-        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
     if env["arch"] == "arm64":
-        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
 
     env.Append(
         LINKFLAGS=[
@@ -228,8 +231,7 @@ def configure(env):
 
     env.Append(CPPDEFINES=["VULKAN_ENABLED"])
     env.Append(LINKFLAGS=["-framework", "IOSurface"])
-    if env["use_static_mvk"]:
-        env.Append(LINKFLAGS=["-framework", "MoltenVK"])
-        env["builtin_vulkan"] = False
-    elif not env["builtin_vulkan"]:
-        env.Append(LIBS=["vulkan"])
+
+    # Use Static Vulkan for iOS. Dynamic Framework works fine too.
+    env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+    env["builtin_vulkan"] = False

+ 58 - 0
platform/iphone/display_layer.h

@@ -0,0 +1,58 @@
+/*************************************************************************/
+/*  display_layer.h                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
+
+@protocol DisplayLayer <NSObject>
+
+- (void)renderDisplayLayer;
+- (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>
+#else
+@interface GodotMetalLayer : CALayer <DisplayLayer>
+#endif
+#else
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#endif
+@end
+
+API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end

+ 186 - 0
platform/iphone/display_layer.mm

@@ -0,0 +1,186 @@
+/*************************************************************************/
+/*  display_layer.mm                                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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_layer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@implementation GodotMetalLayer
+
+- (void)initializeDisplayLayer {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+	if (@available(iOS 13, *)) {
+		// Simulator supports Metal since iOS 13
+	} else {
+		NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
+	}
+#endif
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)renderDisplayLayer {
+}
+
+@end
+
+@implementation GodotOpenGLLayer {
+	// The pixel dimensions of the backbuffer
+	GLint backingWidth;
+	GLint backingHeight;
+
+	EAGLContext *context;
+	GLuint viewRenderbuffer, viewFramebuffer;
+	GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+	// Get our backing layer
+
+	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+	self.opaque = YES;
+	self.drawableProperties = [NSDictionary
+			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+			kEAGLDrawablePropertyRetainedBacking,
+			kEAGLColorFormatRGBA8,
+			kEAGLDrawablePropertyColorFormat,
+			nil];
+
+	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+	// Create GL ES 2 context
+	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
+		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+		NSLog(@"Setting up an OpenGL ES 2.0 context.");
+		if (!context) {
+			NSLog(@"Failed to create OpenGL ES 2.0 context!");
+			return;
+		}
+	}
+
+	if (![EAGLContext setCurrentContext:context]) {
+		NSLog(@"Failed to set EAGLContext!");
+		return;
+	}
+	if (![self createFramebuffer]) {
+		NSLog(@"Failed to create frame buffer!");
+		return;
+	}
+}
+
+- (void)layoutDisplayLayer {
+	[EAGLContext setCurrentContext:context];
+	[self destroyFramebuffer];
+	[self createFramebuffer];
+}
+
+- (void)renderDisplayLayer {
+	[EAGLContext setCurrentContext:context];
+}
+
+- (void)dealloc {
+	if ([EAGLContext currentContext] == context) {
+		[EAGLContext setCurrentContext:nil];
+	}
+
+	if (context) {
+		[context release];
+		context = nil;
+	}
+
+	[super dealloc];
+}
+
+- (BOOL)createFramebuffer {
+	glGenFramebuffersOES(1, &viewFramebuffer);
+	glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
+	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+	glGenRenderbuffersOES(1, &depthRenderbuffer);
+	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+		return NO;
+	}
+
+	//    if (OS::get_singleton()) {
+	//        OS::VideoMode vm;
+	//        vm.fullscreen = true;
+	//        vm.width = backingWidth;
+	//        vm.height = backingHeight;
+	//        vm.resizable = false;
+	//        OS::get_singleton()->set_video_mode(vm);
+	//        OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
+	//    };
+	//    gl_view_base_fb = viewFramebuffer;
+
+	return YES;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+	glDeleteFramebuffersOES(1, &viewFramebuffer);
+	viewFramebuffer = 0;
+	glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+	viewRenderbuffer = 0;
+
+	if (depthRenderbuffer) {
+		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+		depthRenderbuffer = 0;
+	}
+}
+
+@end

+ 202 - 0
platform/iphone/display_server_iphone.h

@@ -0,0 +1,202 @@
+/*************************************************************************/
+/*  display_server_iphone.h                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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.                */
+/*************************************************************************/
+
+#ifndef display_server_iphone_h
+#define display_server_iphone_h
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+
+#include "vulkan_context_iphone.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+class DisplayServerIPhone : public DisplayServer {
+	GDCLASS(DisplayServerIPhone, DisplayServer)
+
+	_THREAD_SAFE_CLASS_
+
+#if defined(VULKAN_ENABLED)
+	VulkanContextIPhone *context_vulkan;
+	RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+	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;
+
+	int virtual_keyboard_height = 0;
+
+	void perform_event(const Ref<InputEvent> &p_event);
+
+	DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+	~DisplayServerIPhone();
+
+public:
+	String rendering_driver;
+
+	static DisplayServerIPhone *get_singleton();
+
+	static void register_iphone_driver();
+	static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, 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;
+
+	// MARK: - Input
+
+	// MARK: Touches
+
+	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
+	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
+	void touches_cancelled(int p_idx);
+
+	// MARK: Keyboard
+
+	void key(uint32_t p_key, bool p_pressed);
+
+	// MARK: Motion
+
+	void update_gravity(float p_x, float p_y, float p_z);
+	void update_accelerometer(float p_x, float p_y, float p_z);
+	void update_magnetometer(float p_x, float p_y, float p_z);
+	void update_gyroscope(float p_x, float p_y, float p_z);
+
+	// MARK: -
+
+	virtual bool has_feature(Feature p_feature) const override;
+	virtual String get_name() const override;
+
+	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+	virtual int get_screen_count() 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 Vector<DisplayServer::WindowID> get_window_list() const override;
+
+	virtual WindowID
+	get_window_at_screen_position(const Point2i &p_position) 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 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_real_size(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 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 bool screen_is_touchscreen(int p_screen) const override;
+
+	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, 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 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;
+
+	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+	virtual bool native_video_is_playing() const override;
+	virtual void native_video_pause() override;
+	virtual void native_video_unpause() override;
+	virtual void native_video_stop() override;
+
+	void resize_window(CGSize size);
+};
+
+#endif /* display_server_iphone_h */

+ 751 - 0
platform/iphone/display_server_iphone.mm

@@ -0,0 +1,751 @@
+/*************************************************************************/
+/*  display_server_iphone.mm                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 "display_server_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/project_settings.h"
+#import "godot_view.h"
+#include "ios.h"
+#include "os_iphone.h"
+#import "view_controller.h"
+
+#import <Foundation/Foundation.h>
+#import <sys/utsname.h>
+
+static const float kDisplayServerIPhoneAcceleration = 1;
+static NSDictionary *iOSModelToDPI = @{
+	@[
+		@"iPad1,1",
+		@"iPad2,1",
+		@"iPad2,2",
+		@"iPad2,3",
+		@"iPad2,4",
+	] : @132,
+	@[
+		@"iPhone1,1",
+		@"iPhone1,2",
+		@"iPhone2,1",
+		@"iPad2,5",
+		@"iPad2,6",
+		@"iPad2,7",
+		@"iPod1,1",
+		@"iPod2,1",
+		@"iPod3,1",
+	] : @163,
+	@[
+		@"iPad3,1",
+		@"iPad3,2",
+		@"iPad3,3",
+		@"iPad3,4",
+		@"iPad3,5",
+		@"iPad3,6",
+		@"iPad4,1",
+		@"iPad4,2",
+		@"iPad4,3",
+		@"iPad5,3",
+		@"iPad5,4",
+		@"iPad6,3",
+		@"iPad6,4",
+		@"iPad6,7",
+		@"iPad6,8",
+		@"iPad6,11",
+		@"iPad6,12",
+		@"iPad7,1",
+		@"iPad7,2",
+		@"iPad7,3",
+		@"iPad7,4",
+		@"iPad7,5",
+		@"iPad7,6",
+		@"iPad7,11",
+		@"iPad7,12",
+		@"iPad8,1",
+		@"iPad8,2",
+		@"iPad8,3",
+		@"iPad8,4",
+		@"iPad8,5",
+		@"iPad8,6",
+		@"iPad8,7",
+		@"iPad8,8",
+		@"iPad8,9",
+		@"iPad8,10",
+		@"iPad8,11",
+		@"iPad8,12",
+		@"iPad11,3",
+		@"iPad11,4",
+	] : @264,
+	@[
+		@"iPhone3,1",
+		@"iPhone3,2",
+		@"iPhone3,3",
+		@"iPhone4,1",
+		@"iPhone5,1",
+		@"iPhone5,2",
+		@"iPhone5,3",
+		@"iPhone5,4",
+		@"iPhone6,1",
+		@"iPhone6,2",
+		@"iPhone7,2",
+		@"iPhone8,1",
+		@"iPhone8,4",
+		@"iPhone9,1",
+		@"iPhone9,3",
+		@"iPhone10,1",
+		@"iPhone10,4",
+		@"iPhone11,8",
+		@"iPhone12,1",
+		@"iPhone12,8",
+		@"iPad4,4",
+		@"iPad4,5",
+		@"iPad4,6",
+		@"iPad4,7",
+		@"iPad4,8",
+		@"iPad4,9",
+		@"iPad5,1",
+		@"iPad5,2",
+		@"iPad11,1",
+		@"iPad11,2",
+		@"iPod4,1",
+		@"iPod5,1",
+		@"iPod7,1",
+		@"iPod9,1",
+	] : @326,
+	@[
+		@"iPhone7,1",
+		@"iPhone8,2",
+		@"iPhone9,2",
+		@"iPhone9,4",
+		@"iPhone10,2",
+		@"iPhone10,5",
+	] : @401,
+	@[
+		@"iPhone10,3",
+		@"iPhone10,6",
+		@"iPhone11,2",
+		@"iPhone11,4",
+		@"iPhone11,6",
+		@"iPhone12,3",
+		@"iPhone12,5",
+	] : @458,
+};
+
+DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
+	return (DisplayServerIPhone *)DisplayServer::get_singleton();
+}
+
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+	rendering_driver = p_rendering_driver;
+
+#if defined(OPENGL_ENABLED)
+	// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
+	// again,
+
+	if (rendering_driver == "opengl_es") {
+		bool gl_initialization_error = false;
+
+		// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+		if (RasterizerGLES2::is_viable() == OK) {
+			RasterizerGLES2::register_config();
+			RasterizerGLES2::make_current();
+		} else {
+			gl_initialization_error = true;
+		}
+
+		if (gl_initialization_error) {
+			OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
+			//        return ERR_UNAVAILABLE;
+		}
+
+		//    rendering_server = memnew(RenderingServerRaster);
+		//    // FIXME: Reimplement threaded rendering
+		//    if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+		//        rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+		//        false));
+		//    }
+		//    rendering_server->init();
+		// rendering_server->cursor_set_visible(false, 0);
+
+		// reset this to what it should be, it will have been set to 0 after
+		// rendering_server->init() is called
+		//    RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+	}
+#endif
+
+#if defined(VULKAN_ENABLED)
+	rendering_driver = "vulkan";
+
+	context_vulkan = nullptr;
+	rendering_device_vulkan = nullptr;
+
+	if (rendering_driver == "vulkan") {
+		context_vulkan = memnew(VulkanContextIPhone);
+		if (context_vulkan->initialize() != OK) {
+			memdelete(context_vulkan);
+			context_vulkan = nullptr;
+			ERR_FAIL_MSG("Failed to initialize Vulkan context");
+		}
+
+		CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+
+		if (!layer) {
+			ERR_FAIL_MSG("Failed to create iOS rendering layer.");
+		}
+
+		Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+		if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
+			memdelete(context_vulkan);
+			context_vulkan = nullptr;
+			ERR_FAIL_MSG("Failed to create Vulkan window.");
+		}
+
+		rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+		rendering_device_vulkan->initialize(context_vulkan);
+
+		RasterizerRD::make_current();
+	}
+#endif
+
+	bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+	screen_set_keep_on(keep_screen_on);
+
+	Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+	r_error = OK;
+}
+
+DisplayServerIPhone::~DisplayServerIPhone() {
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+		if (rendering_device_vulkan) {
+			rendering_device_vulkan->finalize();
+			memdelete(rendering_device_vulkan);
+			rendering_device_vulkan = NULL;
+		}
+
+		if (context_vulkan) {
+			context_vulkan->window_destroy(MAIN_WINDOW_ID);
+			memdelete(context_vulkan);
+			context_vulkan = NULL;
+		}
+	}
+#endif
+}
+
+DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+	return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
+	Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+	drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+	drivers.push_back("opengl_es");
+#endif
+
+	return drivers;
+}
+
+void DisplayServerIPhone::register_iphone_driver() {
+	register_create_function("iphone", create_func, get_rendering_drivers_func);
+}
+
+// MARK: Events
+
+void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+	window_resize_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+	window_event_callback = p_callable;
+}
+void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+	input_event_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+	input_text_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerIPhone::process_events() {
+}
+
+void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+	DisplayServerIPhone::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
+	_window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerIPhone::send_input_text(const String &p_text) const {
+	_window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
+	_window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+	if (!p_callable.is_null()) {
+		const Variant *argp = &p_arg;
+		Variant ret;
+		Callable::CallError ce;
+		p_callable.call((const Variant **)&argp, 1, ret, ce);
+	}
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+	if (!GLOBAL_DEF("debug/disable_touch", false)) {
+		Ref<InputEventScreenTouch> ev;
+		ev.instance();
+
+		ev->set_index(p_idx);
+		ev->set_pressed(p_pressed);
+		ev->set_position(Vector2(p_x, p_y));
+		perform_event(ev);
+	}
+};
+
+void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+	if (!GLOBAL_DEF("debug/disable_touch", false)) {
+		Ref<InputEventScreenDrag> ev;
+		ev.instance();
+		ev->set_index(p_idx);
+		ev->set_position(Vector2(p_x, p_y));
+		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+		perform_event(ev);
+	};
+};
+
+void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
+	Input::get_singleton()->parse_input_event(p_event);
+};
+
+void DisplayServerIPhone::touches_cancelled(int p_idx) {
+	touch_press(p_idx, -1, -1, false, false);
+};
+
+// MARK: Keyboard
+
+void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
+	Ref<InputEventKey> ev;
+	ev.instance();
+	ev->set_echo(false);
+	ev->set_pressed(p_pressed);
+	ev->set_keycode(p_key);
+	ev->set_physical_keycode(p_key);
+	ev->set_unicode(p_key);
+	perform_event(ev);
+};
+
+// MARK: Motion
+
+void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
+	Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
+		float p_z) {
+	// Found out the Z should not be negated! Pass as is!
+	Vector3 v_accelerometer = Vector3(
+			p_x / kDisplayServerIPhoneAcceleration,
+			p_y / kDisplayServerIPhoneAcceleration,
+			p_z / kDisplayServerIPhoneAcceleration);
+
+	Input::get_singleton()->set_accelerometer(v_accelerometer);
+
+	/*
+  if (p_x != last_accel.x) {
+      //printf("updating accel x %f\n", p_x);
+      InputEvent ev;
+      ev.type = InputEvent::JOYPAD_MOTION;
+      ev.device = 0;
+      ev.joy_motion.axis = JOY_ANALOG_0;
+      ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
+      last_accel.x = p_x;
+      queue_event(ev);
+  };
+  if (p_y != last_accel.y) {
+      //printf("updating accel y %f\n", p_y);
+      InputEvent ev;
+      ev.type = InputEvent::JOYPAD_MOTION;
+      ev.device = 0;
+      ev.joy_motion.axis = JOY_ANALOG_1;
+      ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
+      last_accel.y = p_y;
+      queue_event(ev);
+  };
+  if (p_z != last_accel.z) {
+      //printf("updating accel z %f\n", p_z);
+      InputEvent ev;
+      ev.type = InputEvent::JOYPAD_MOTION;
+      ev.device = 0;
+      ev.joy_motion.axis = JOY_ANALOG_2;
+      ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
+      last_accel.z = p_z;
+      queue_event(ev);
+  };
+  */
+};
+
+void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
+	Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
+	Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
+};
+
+// MARK: -
+
+bool DisplayServerIPhone::has_feature(Feature p_feature) const {
+	switch (p_feature) {
+		// case FEATURE_CONSOLE_WINDOW:
+		// case FEATURE_CURSOR_SHAPE:
+		// case FEATURE_CUSTOM_CURSOR_SHAPE:
+		// case FEATURE_GLOBAL_MENU:
+		// case FEATURE_HIDPI:
+		// case FEATURE_ICON:
+		// case FEATURE_IME:
+		// case FEATURE_MOUSE:
+		// case FEATURE_MOUSE_WARP:
+		// case FEATURE_NATIVE_DIALOG:
+		// case FEATURE_NATIVE_ICON:
+		// case FEATURE_NATIVE_VIDEO:
+		// case FEATURE_WINDOW_TRANSPARENCY:
+		case FEATURE_CLIPBOARD:
+		case FEATURE_KEEP_SCREEN_ON:
+		case FEATURE_ORIENTATION:
+		case FEATURE_TOUCHSCREEN:
+		case FEATURE_VIRTUAL_KEYBOARD:
+			return true;
+		default:
+			return false;
+	}
+}
+
+String DisplayServerIPhone::get_name() const {
+	return "iPhone";
+}
+
+void DisplayServerIPhone::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());
+}
+
+int DisplayServerIPhone::get_screen_count() const {
+	return 1;
+}
+
+Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
+	return Size2i();
+}
+
+Size2i DisplayServerIPhone::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 DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
+	return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+}
+
+int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
+	struct utsname systemInfo;
+	uname(&systemInfo);
+
+	NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+	for (NSArray *keyArray in iOSModelToDPI) {
+		if ([keyArray containsObject:string]) {
+			NSNumber *value = iOSModelToDPI[keyArray];
+			return [value intValue];
+		}
+	}
+
+	return 163;
+}
+
+float DisplayServerIPhone::screen_get_scale(int p_screen) const {
+	return [UIScreen mainScreen].nativeScale;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
+	Vector<DisplayServer::WindowID> list;
+	list.push_back(MAIN_WINDOW_ID);
+	return list;
+}
+
+DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
+	return MAIN_WINDOW_ID;
+}
+
+void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+	window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
+	return window_attached_instance_id;
+}
+
+void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
+	return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
+	return Point2i();
+}
+
+void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
+	// Probably not supported for single window iOS app
+}
+
+void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
+	return Size2i();
+}
+
+void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
+	return Size2i();
+}
+
+void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::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 DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
+	return window_get_size(p_window);
+}
+
+void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
+	return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
+	return false;
+}
+
+void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+	return false;
+}
+
+void DisplayServerIPhone::window_request_attention(WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
+	// Probably not supported for iOS
+}
+
+float DisplayServerIPhone::screen_get_max_scale() const {
+	return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+};
+
+void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+	screen_orientation = p_orientation;
+}
+
+DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
+	return screen_orientation;
+}
+
+bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
+	return true;
+}
+
+bool DisplayServerIPhone::can_any_window_draw() const {
+	return true;
+}
+
+bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
+	return true;
+}
+
+void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
+	[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+}
+
+void DisplayServerIPhone::virtual_keyboard_hide() {
+	[AppDelegate.viewController.godotView resignFirstResponder];
+}
+
+void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
+	virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerIPhone::virtual_keyboard_get_height() const {
+	return virtual_keyboard_height;
+}
+
+void DisplayServerIPhone::clipboard_set(const String &p_text) {
+	[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
+}
+
+String DisplayServerIPhone::clipboard_get() const {
+	NSString *text = [UIPasteboard generalPasteboard].string;
+
+	return String::utf8([text UTF8String]);
+}
+
+void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
+	[UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerIPhone::screen_is_kept_on() const {
+	return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
+	FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+	bool exists = f && f->is_open();
+
+	String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+	if (!exists) {
+		return FAILED;
+	}
+
+	String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
+
+	if (p_path.begins_with("res://")) {
+		if (PackedData::get_singleton()->has_path(p_path)) {
+			printf("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
+			return ERR_INVALID_PARAMETER;
+		} else {
+			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
+		}
+	} else if (p_path.begins_with("user://")) {
+		p_path = p_path.replace("user:/", user_data_dir);
+	}
+
+	memdelete(f);
+
+	printf("Playing video: %S\n", p_path.c_str());
+
+	String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+	NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease];
+	NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+	NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+	if (![AppDelegate.viewController playVideoAtPath:filePath
+											  volume:p_volume
+											   audio:audioTrack
+											subtitle:subtitleTrack]) {
+		return OK;
+	}
+
+	return FAILED;
+}
+
+bool DisplayServerIPhone::native_video_is_playing() const {
+	return [AppDelegate.viewController isVideoPlaying];
+}
+
+void DisplayServerIPhone::native_video_pause() {
+	if (native_video_is_playing()) {
+		[AppDelegate.viewController pauseVideo];
+	}
+}
+
+void DisplayServerIPhone::native_video_unpause() {
+	[AppDelegate.viewController unpauseVideo];
+};
+
+void DisplayServerIPhone::native_video_stop() {
+	if (native_video_is_playing()) {
+		[AppDelegate.viewController stopVideo];
+	}
+}
+
+void DisplayServerIPhone::resize_window(CGSize viewSize) {
+	Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
+
+#if defined(VULKAN_ENABLED)
+	if (rendering_driver == "vulkan") {
+		if (context_vulkan) {
+			context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
+		}
+	}
+#endif
+
+	Variant resize_rect = Rect2i(Point2i(), size);
+	_window_callback(window_resize_callback, resize_rect);
+}

+ 3 - 3
platform/iphone/game_center.h

@@ -51,12 +51,12 @@ public:
 	void connect();
 	bool is_authenticated();
 
-	Error post_score(Variant p_score);
-	Error award_achievement(Variant p_params);
+	Error post_score(Dictionary p_score);
+	Error award_achievement(Dictionary p_params);
 	void reset_achievements();
 	void request_achievements();
 	void request_achievement_descriptions();
-	Error show_game_center(Variant p_params);
+	Error show_game_center(Dictionary p_params);
 	Error request_identity_verification_signature();
 
 	void game_center_closed();

+ 36 - 25
platform/iphone/game_center.mm

@@ -47,13 +47,15 @@ extern "C" {
 #import "app_delegate.h"
 };
 
+#import "view_controller.h"
+
 GameCenter *GameCenter::instance = NULL;
 
 void GameCenter::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
 
 	ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
-	ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
+	ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
 	ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
 	ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
 	ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
@@ -105,7 +107,14 @@ void GameCenter::connect() {
 			ret["type"] = "authentication";
 			if (player.isAuthenticated) {
 				ret["result"] = "ok";
-				ret["player_id"] = [player.playerID UTF8String];
+				if (@available(iOS 13, *)) {
+					ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+				} else {
+					ret["player_id"] = [player.playerID UTF8String];
+#endif
+				}
+
 				GameCenter::get_singleton()->authenticated = true;
 			} else {
 				ret["result"] = "error";
@@ -123,11 +132,10 @@ bool GameCenter::is_authenticated() {
 	return authenticated;
 };
 
-Error GameCenter::post_score(Variant p_score) {
-	Dictionary params = p_score;
-	ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
-	float score = params["score"];
-	String category = params["category"];
+Error GameCenter::post_score(Dictionary p_score) {
+	ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
+	float score = p_score["score"];
+	String category = p_score["category"];
 
 	NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
 	GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
@@ -153,11 +161,10 @@ Error GameCenter::post_score(Variant p_score) {
 	return OK;
 };
 
-Error GameCenter::award_achievement(Variant p_params) {
-	Dictionary params = p_params;
-	ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
-	String name = params["name"];
-	float progress = params["progress"];
+Error GameCenter::award_achievement(Dictionary p_params) {
+	ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
+	String name = p_params["name"];
+	float progress = p_params["progress"];
 
 	NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
 	GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
@@ -167,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {
 
 	achievement.percentComplete = progress;
 	achievement.showsCompletionBanner = NO;
-	if (params.has("show_completion_banner")) {
-		achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
+	if (p_params.has("show_completion_banner")) {
+		achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
 	}
 
 	[GKAchievement reportAchievements:@[ achievement ]
@@ -202,7 +209,7 @@ void GameCenter::request_achievement_descriptions() {
 			Array hidden;
 			Array replayable;
 
-			for (int i = 0; i < [descriptions count]; i++) {
+			for (NSUInteger i = 0; i < [descriptions count]; i++) {
 				GKAchievementDescription *description = [descriptions objectAtIndex:i];
 
 				const char *str = [description.identifier UTF8String];
@@ -250,7 +257,7 @@ void GameCenter::request_achievements() {
 			PackedStringArray names;
 			PackedFloat32Array percentages;
 
-			for (int i = 0; i < [achievements count]; i++) {
+			for (NSUInteger i = 0; i < [achievements count]; i++) {
 				GKAchievement *achievement = [achievements objectAtIndex:i];
 				const char *str = [achievement.identifier UTF8String];
 				names.push_back(String::utf8(str != NULL ? str : ""));
@@ -285,14 +292,12 @@ void GameCenter::reset_achievements() {
 	}];
 };
 
-Error GameCenter::show_game_center(Variant p_params) {
+Error GameCenter::show_game_center(Dictionary p_params) {
 	ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
 
-	Dictionary params = p_params;
-
 	GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
-	if (params.has("view")) {
-		String view_name = params["view"];
+	if (p_params.has("view")) {
+		String view_name = p_params["view"];
 		if (view_name == "default") {
 			view_state = GKGameCenterViewControllerStateDefault;
 		} else if (view_name == "leaderboards") {
@@ -306,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {
 		}
 	}
 
-	GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
+	GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];
 	ERR_FAIL_COND_V(!controller, FAILED);
 
 	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
@@ -316,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {
 	controller.viewState = view_state;
 	if (view_state == GKGameCenterViewControllerStateLeaderboards) {
 		controller.leaderboardIdentifier = nil;
-		if (params.has("leaderboard_name")) {
-			String name = params["leaderboard_name"];
+		if (p_params.has("leaderboard_name")) {
+			String name = p_params["leaderboard_name"];
 			NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
 			controller.leaderboardIdentifier = name_str;
 		}
@@ -341,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {
 			ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
 			ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
 			ret["timestamp"] = timestamp;
-			ret["player_id"] = [player.playerID UTF8String];
+			if (@available(iOS 13, *)) {
+				ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+			} else {
+				ret["player_id"] = [player.playerID UTF8String];
+#endif
+			}
 		} else {
 			ret["result"] = "error";
 			ret["error_code"] = (int64_t)error.code;

+ 0 - 123
platform/iphone/gl_view.h

@@ -1,123 +0,0 @@
-/*************************************************************************/
-/*  gl_view.h                                                            */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* 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 <AVFoundation/AVFoundation.h>
-#import <MediaPlayer/MediaPlayer.h>
-#import <OpenGLES/EAGL.h>
-#import <OpenGLES/ES1/gl.h>
-#import <OpenGLES/ES1/glext.h>
-#import <UIKit/UIKit.h>
-
-@protocol GLViewDelegate;
-
-@interface GLView : UIView <UIKeyInput> {
-@private
-	// The pixel dimensions of the backbuffer
-	GLint backingWidth;
-	GLint backingHeight;
-
-	EAGLContext *context;
-
-	// OpenGL names for the renderbuffer and framebuffers used to render to this view
-	GLuint viewRenderbuffer, viewFramebuffer;
-
-	// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
-	GLuint depthRenderbuffer;
-
-	BOOL useCADisplayLink;
-	// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
-	CADisplayLink *displayLink;
-
-	// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
-	// Only used if CADisplayLink is not
-	NSTimer *animationTimer;
-
-	NSTimeInterval animationInterval;
-
-	// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
-	id<GLViewDelegate> delegate;
-
-	// Flag to denote that the -setupView method of a delegate has been called.
-	// Resets to NO whenever the delegate changes.
-	BOOL delegateSetup;
-	BOOL active;
-	float screen_scale;
-}
-
-@property(nonatomic, assign) id<GLViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property NSTimeInterval animationInterval;
-@property(nonatomic, assign) BOOL useCADisplayLink;
-
-@end
-
-@protocol GLViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GLView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GLView *)view;
-
-@end

+ 0 - 702
platform/iphone/gl_view.mm

@@ -1,702 +0,0 @@
-/*************************************************************************/
-/*  gl_view.mm                                                           */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* 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 "gl_view.h"
-
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "os_iphone.h"
-#include "servers/audio_server.h"
-
-#import <OpenGLES/EAGLDrawable.h>
-#import <QuartzCore/QuartzCore.h>
-
-/*
-@interface GLView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-int gl_view_base_fb;
-static String keyboard_text;
-static GLView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
-	keyboard_text = p_existing;
-	printf("instance on show is %p\n", _instance);
-	[_instance open_keyboard];
-};
-
-void _hide_keyboard() {
-	printf("instance on hide is %p\n", _instance);
-	[_instance hide_keyboard];
-	keyboard_text = "";
-};
-
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
-	UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
-	if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
-		insets = [_instance safeAreaInsets];
-	}
-	ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
-			Rect2(0, 0, p_window_width, p_window_height));
-	UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
-	return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
-}
-
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
-	p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
-	NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
-
-	_instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
-
-	_instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
-	[_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-
-	_instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
-	_instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
-
-	[_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-	[[NSNotificationCenter defaultCenter]
-			addObserver:_instance
-			   selector:@selector(playerItemDidReachEnd:)
-				   name:AVPlayerItemDidPlayToEndTimeNotification
-				 object:[_instance.avPlayer currentItem]];
-
-	[_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
-	[_instance.avPlayerLayer setFrame:_instance.bounds];
-	[_instance.layer addSublayer:_instance.avPlayerLayer];
-	[_instance.avPlayer play];
-
-	AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
-	NSMutableArray *allAudioParams = [NSMutableArray array];
-	for (id track in audioGroup.options) {
-		NSString *language = [[track locale] localeIdentifier];
-		NSLog(@"subtitle lang: %@", language);
-
-		if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
-			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
-			[audioInputParams setVolume:p_volume atTime:kCMTimeZero];
-			[audioInputParams setTrackID:[track trackID]];
-			[allAudioParams addObject:audioInputParams];
-
-			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
-			[audioMix setInputParameters:allAudioParams];
-
-			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
-			[_instance.avPlayer.currentItem setAudioMix:audioMix];
-
-			break;
-		}
-	}
-
-	AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
-	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
-	for (id track in useableTracks) {
-		NSString *language = [[track locale] localeIdentifier];
-		NSLog(@"subtitle lang: %@", language);
-
-		if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
-			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
-			break;
-		}
-	}
-
-	video_playing = true;
-
-	return true;
-}
-
-bool _is_video_playing() {
-	if (_instance.avPlayer.error) {
-		printf("Error during playback\n");
-	}
-	return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
-
-void _pause_video() {
-	video_current_time = _instance.avPlayer.currentTime;
-	[_instance.avPlayer pause];
-	video_playing = false;
-}
-
-void _focus_out_video() {
-	printf("focus out pausing video\n");
-	[_instance.avPlayer pause];
-};
-
-void _unpause_video() {
-	[_instance.avPlayer play];
-	video_playing = true;
-};
-
-void _stop_video() {
-	[_instance.avPlayer pause];
-	[_instance.avPlayerLayer removeFromSuperlayer];
-	_instance.avPlayer = nil;
-	video_playing = false;
-}
-
-CGFloat _points_to_pixels(CGFloat points) {
-	float pixelPerInch;
-	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
-		pixelPerInch = 132;
-	} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
-		pixelPerInch = 163;
-	} else {
-		pixelPerInch = 160;
-	}
-	CGFloat pointsPerInch = 72.0;
-	return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GLView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
-	for (int i = 0; i < max_touches; i++) {
-		touches[i] = NULL;
-	};
-};
-
-static int get_touch_id(UITouch *p_touch) {
-	int first = -1;
-	for (int i = 0; i < max_touches; i++) {
-		if (first == -1 && touches[i] == NULL) {
-			first = i;
-			continue;
-		};
-		if (touches[i] == p_touch)
-			return i;
-	};
-
-	if (first != -1) {
-		touches[first] = p_touch;
-		return first;
-	};
-
-	return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
-	int remaining = 0;
-	for (int i = 0; i < max_touches; i++) {
-		if (touches[i] == NULL)
-			continue;
-		if (touches[i] == p_touch)
-			touches[i] = NULL;
-		else
-			++remaining;
-	};
-	return remaining;
-};
-
-static void clear_touches() {
-	for (int i = 0; i < max_touches; i++) {
-		touches[i] = NULL;
-	};
-};
-
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
-	return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
-	active = FALSE;
-	if ((self = [super initWithCoder:coder])) {
-		self = [self initGLES];
-	}
-	return self;
-}
-
-- (id)initGLES {
-	// Get our backing layer
-	CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
-	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
-	eaglLayer.opaque = YES;
-	eaglLayer.drawableProperties = [NSDictionary
-			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
-			kEAGLDrawablePropertyRetainedBacking,
-			kEAGLColorFormatRGBA8,
-			kEAGLDrawablePropertyColorFormat,
-			nil];
-
-	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
-	// Create GL ES 2 context
-	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
-		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
-		NSLog(@"Setting up an OpenGL ES 2.0 context.");
-		if (!context) {
-			NSLog(@"Failed to create OpenGL ES 2.0 context!");
-			return nil;
-		}
-	}
-
-	if (![EAGLContext setCurrentContext:context]) {
-		NSLog(@"Failed to set EAGLContext!");
-		return nil;
-	}
-	if (![self createFramebuffer]) {
-		NSLog(@"Failed to create frame buffer!");
-		return nil;
-	}
-
-	// Default the animation interval to 1/60th of a second.
-	animationInterval = 1.0 / 60.0;
-	return self;
-}
-
-- (id<GLViewDelegate>)delegate {
-	return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GLViewDelegate>)d {
-	delegate = d;
-	delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
-}
-
-@synthesize useCADisplayLink;
-
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
-	[EAGLContext setCurrentContext:context];
-	[self destroyFramebuffer];
-	[self createFramebuffer];
-	[self drawView];
-}
-
-- (BOOL)createFramebuffer {
-	// Generate IDs for a framebuffer object and a color renderbuffer
-	UIScreen *mainscr = [UIScreen mainScreen];
-	printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
-	self.contentScaleFactor = mainscr.nativeScale;
-
-	glGenFramebuffersOES(1, &viewFramebuffer);
-	glGenRenderbuffersOES(1, &viewRenderbuffer);
-
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
-	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
-	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
-	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
-	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
-	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
-	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
-	glGenRenderbuffersOES(1, &depthRenderbuffer);
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
-	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
-	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
-	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
-		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
-		return NO;
-	}
-
-	if (OS::get_singleton()) {
-		OS::VideoMode vm;
-		vm.fullscreen = true;
-		vm.width = backingWidth;
-		vm.height = backingHeight;
-		vm.resizable = false;
-		OS::get_singleton()->set_video_mode(vm);
-		OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
-	};
-	gl_view_base_fb = viewFramebuffer;
-
-	return YES;
-}
-
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
-	glDeleteFramebuffersOES(1, &viewFramebuffer);
-	viewFramebuffer = 0;
-	glDeleteRenderbuffersOES(1, &viewRenderbuffer);
-	viewRenderbuffer = 0;
-
-	if (depthRenderbuffer) {
-		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
-		depthRenderbuffer = 0;
-	}
-}
-
-- (void)startAnimation {
-	if (active)
-		return;
-	active = TRUE;
-	printf("start animation!\n");
-	if (useCADisplayLink) {
-		// Approximate frame rate
-		// assumes device refreshes at 60 fps
-		int frameInterval = (int)floor(animationInterval * 60.0f);
-
-		displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
-		[displayLink setFrameInterval:frameInterval];
-
-		// Setup DisplayLink in main thread
-		[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
-	} else {
-		animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
-	}
-
-	if (video_playing) {
-		_unpause_video();
-	}
-}
-
-- (void)stopAnimation {
-	if (!active)
-		return;
-	active = FALSE;
-	printf("******** stop animation!\n");
-
-	if (useCADisplayLink) {
-		[displayLink invalidate];
-		displayLink = nil;
-	} else {
-		[animationTimer invalidate];
-		animationTimer = nil;
-	}
-
-	clear_touches();
-
-	if (video_playing) {
-		// save position
-	}
-}
-
-- (void)setAnimationInterval:(NSTimeInterval)interval {
-	animationInterval = interval;
-	if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
-		[self stopAnimation];
-		[self startAnimation];
-	}
-}
-
-// Updates the OpenGL view when the timer fires
-- (void)drawView {
-	if (!active) {
-		printf("draw view not active!\n");
-		return;
-	};
-	if (useCADisplayLink) {
-		// Pause the CADisplayLink to avoid recursion
-		[displayLink setPaused:YES];
-
-		// Process all input events
-		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
-			;
-
-		// We are good to go, resume the CADisplayLink
-		[displayLink setPaused:NO];
-	}
-
-	// Make sure that you are drawing to the current context
-	[EAGLContext setCurrentContext:context];
-
-	// If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
-	if (!delegateSetup) {
-		[delegate setupView:self];
-		delegateSetup = YES;
-	}
-
-	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
-	[delegate drawView:self];
-
-	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
-	[context presentRenderbuffer:GL_RENDERBUFFER_OES];
-
-#ifdef DEBUG_ENABLED
-	GLenum err = glGetError();
-	if (err)
-		NSLog(@"DrawView: %x error", err);
-#endif
-}
-
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
-			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseBegan)
-				continue;
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			CGPoint touchPoint = [touch locationInView:self];
-			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
-		};
-	};
-}
-
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
-			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseMoved)
-				continue;
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			CGPoint touchPoint = [touch locationInView:self];
-			CGPoint prev_point = [touch previousLocationInView:self];
-			OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
-		};
-	};
-}
-
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
-	NSArray *tlist = [[event allTouches] allObjects];
-	for (unsigned int i = 0; i < [tlist count]; i++) {
-		if ([touches containsObject:[tlist objectAtIndex:i]]) {
-			UITouch *touch = [tlist objectAtIndex:i];
-			if (touch.phase != UITouchPhaseEnded)
-				continue;
-			int tid = get_touch_id(touch);
-			ERR_FAIL_COND(tid == -1);
-			remove_touch(touch);
-			CGPoint touchPoint = [touch locationInView:self];
-			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
-		};
-	};
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
-	OSIPhone::get_singleton()->touches_cancelled();
-	clear_touches();
-};
-
-- (BOOL)canBecomeFirstResponder {
-	return YES;
-};
-
-- (void)open_keyboard {
-	//keyboard_text = p_existing;
-	[self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
-	//keyboard_text = p_existing;
-	[self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
-	NSDictionary *info = notification.userInfo;
-	NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
-	CGRect rawFrame = [value CGRectValue];
-	CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
-
-	OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
-}
-
-- (void)keyboardHidden:(NSNotification *)notification {
-	OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
-}
-
-- (void)deleteBackward {
-	if (keyboard_text.length())
-		keyboard_text.erase(keyboard_text.length() - 1, 1);
-	OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
-
-- (BOOL)hasText {
-	return keyboard_text.length() ? YES : NO;
-};
-
-- (void)insertText:(NSString *)p_text {
-	String character;
-	character.parse_utf8([p_text UTF8String]);
-	keyboard_text = keyboard_text + character;
-	OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
-	printf("inserting text with character %lc\n", (CharType)character[0]);
-};
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
-	printf("*********** route changed!\n");
-	NSDictionary *interuptionDict = notification.userInfo;
-
-	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
-	switch (routeChangeReason) {
-		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
-			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
-			NSLog(@"Headphone/Line plugged in");
-		}; break;
-
-		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
-			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
-			NSLog(@"Headphone/Line was pulled. Resuming video play....");
-			if (_is_video_playing()) {
-				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
-					[_instance.avPlayer play]; // NOTE: change this line according your current player implementation
-					NSLog(@"resumed play");
-				});
-			};
-		}; break;
-
-		case AVAudioSessionRouteChangeReasonCategoryChange: {
-			// called at start - also when other audio wants to play
-			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
-		}; break;
-	}
-}
-
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
-	self = [super initWithFrame:frame];
-	_instance = self;
-	printf("after init super %p\n", self);
-	if (self != nil) {
-		self = [self initGLES];
-		printf("after init gles %p\n", self);
-	}
-	init_touches();
-	self.multipleTouchEnabled = YES;
-	self.autocorrectionType = UITextAutocorrectionTypeNo;
-
-	printf("******** adding observer for sound routing changes\n");
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(audioRouteChangeListenerCallback:)
-				   name:AVAudioSessionRouteChangeNotification
-				 object:nil];
-
-	printf("******** adding observer for keyboard show/hide\n");
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(keyboardOnScreen:)
-				   name:UIKeyboardDidShowNotification
-				 object:nil];
-	[[NSNotificationCenter defaultCenter]
-			addObserver:self
-			   selector:@selector(keyboardHidden:)
-				   name:UIKeyboardDidHideNotification
-				 object:nil];
-
-	//self.autoresizesSubviews = YES;
-	//[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
-
-	return self;
-}
-
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-//	return YES;
-//}
-
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-//	return YES;
-//}
-
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
-	[self stopAnimation];
-
-	if ([EAGLContext currentContext] == context) {
-		[EAGLContext setCurrentContext:nil];
-	}
-
-	[context release];
-	context = nil;
-
-	[super dealloc];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
-	if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
-		if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
-			_stop_video();
-			video_found_error = true;
-		}
-
-		if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
-				_instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
-				CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
-			//NSLog(@"time: %@", video_current_time);
-
-			[_instance.avPlayer seekToTime:video_current_time];
-			video_current_time = kCMTimeZero;
-		}
-	}
-
-	if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
-		NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
-		if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
-			dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
-				[_instance.avPlayer play]; // NOTE: change this line according your current player implementation
-				NSLog(@"resumed play");
-			});
-
-			NSLog(@" . . . PAUSED (or just started)");
-		}
-	}
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
-	_stop_video();
-}
-
-@end

+ 47 - 9
platform/iphone/godot_iphone.cpp → platform/iphone/godot_iphone.mm

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  godot_iphone.cpp                                                     */
+/*  godot_iphone.mm                                                      */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -38,19 +38,53 @@
 
 static OSIPhone *os = nullptr;
 
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
+int add_path(int, char **);
+int add_cmdline(int, char **);
+int iphone_main(int, char **, String);
+
+int add_path(int p_argc, char **p_args) {
+	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+	if (!str) {
+		return p_argc;
+	}
+
+	p_args[p_argc++] = (char *)"--path";
+	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
+	p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+	p_args[p_argc] = NULL;
+	[str release];
+
+	return p_argc;
 };
 
-int iphone_main(int, int, int, char **, String);
+int add_cmdline(int p_argc, char **p_args) {
+	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+	if (!arr) {
+		return p_argc;
+	}
+
+	for (NSUInteger i = 0; i < [arr count]; i++) {
+		NSString *str = [arr objectAtIndex:i];
+		if (!str) {
+			continue;
+		}
+		[str retain]; // @todo delete these at some point
+		p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+		[str release];
+	};
 
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+	p_args[p_argc] = NULL;
+
+	return p_argc;
+};
+
+int iphone_main(int argc, char **argv, String data_dir) {
 	size_t len = strlen(argv[0]);
 
 	while (len--) {
-		if (argv[0][len] == '/')
+		if (argv[0][len] == '/') {
 			break;
+		}
 	}
 
 	if (len >= 0) {
@@ -65,7 +99,7 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
 	char cwd[512];
 	getcwd(cwd, sizeof(cwd));
 	printf("cwd %s\n", cwd);
-	os = new OSIPhone(width, height, data_dir);
+	os = new OSIPhone(data_dir);
 
 	// We must override main when testing is enabled
 	TEST_MAIN_OVERRIDE
@@ -79,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
 	argc = add_cmdline(argc, fargv);
 
 	printf("os created\n");
+
 	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
 	printf("setup %i\n", err);
-	if (err != OK)
+	if (err != OK) {
 		return 255;
+	}
+
+	os->initialize_modules();
 
 	return 0;
 };

+ 56 - 0
platform/iphone/godot_view.h

@@ -0,0 +1,56 @@
+/*************************************************************************/
+/*  godot_view.h                                                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 <UIKit/UIKit.h>
+
+class String;
+
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
+
+@interface GodotView : UIView <UIKeyInput>
+
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
+
+@property(assign, readonly, nonatomic) BOOL isActive;
+
+@property(assign, nonatomic) BOOL useCADisplayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
+
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+- (void)stopRendering;
+- (void)startRendering;
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing;
+
+@end

+ 498 - 0
platform/iphone/godot_view.mm

@@ -0,0 +1,498 @@
+/*************************************************************************/
+/*  godot_view.mm                                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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.h"
+#include "core/os/keyboard.h"
+#include "core/ustring.h"
+#import "display_layer.h"
+#include "display_server_iphone.h"
+#import "godot_view_renderer.h"
+
+#import <CoreMotion/CoreMotion.h>
+
+static const int max_touches = 8;
+
+@interface GodotView () {
+	UITouch *godot_touches[max_touches];
+	String keyboard_text;
+}
+
+@property(assign, nonatomic) BOOL isActive;
+
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
+
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
+
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+
+@property(strong, nonatomic) CMMotionManager *motionManager;
+
+@end
+
+@implementation GodotView
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+	if (self.renderingLayer) {
+		return self.renderingLayer;
+	}
+
+	CALayer<DisplayLayer> *layer;
+
+	if ([driverName isEqualToString:@"vulkan"]) {
+		layer = [GodotMetalLayer layer];
+	} else if ([driverName isEqualToString:@"opengl_es"]) {
+		if (@available(iOS 13, *)) {
+			NSLog(@"OpenGL ES is deprecated on iOS 13");
+		}
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+		return nil;
+#else
+		layer = [GodotOpenGLLayer layer];
+#endif
+	} else {
+		return nil;
+	}
+
+	layer.frame = self.bounds;
+	layer.contentsScale = self.contentScaleFactor;
+
+	[self.layer addSublayer:layer];
+	self.renderingLayer = layer;
+
+	[layer initializeDisplayLayer];
+
+	return self.renderingLayer;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+	self = [super initWithCoder:coder];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+	self = [super initWithFrame:frame];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (void)dealloc {
+	[self stopRendering];
+
+	self.renderer = nil;
+
+	if (self.renderingLayer) {
+		[self.renderingLayer removeFromSuperlayer];
+		self.renderingLayer = nil;
+	}
+
+	if (self.motionManager) {
+		[self.motionManager stopDeviceMotionUpdates];
+		self.motionManager = nil;
+	}
+
+	if (self.displayLink) {
+		[self.displayLink invalidate];
+		self.displayLink = nil;
+	}
+
+	if (self.animationTimer) {
+		[self.animationTimer invalidate];
+		self.animationTimer = nil;
+	}
+
+	[super dealloc];
+}
+
+- (void)godot_commonInit {
+	self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
+
+	[self initTouches];
+
+	// Configure and start accelerometer
+	if (!self.motionManager) {
+		self.motionManager = [[[CMMotionManager alloc] init] autorelease];
+		if (self.motionManager.deviceMotionAvailable) {
+			self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+			[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+		} else {
+			self.motionManager = nil;
+		}
+	}
+}
+
+- (void)stopRendering {
+	if (!self.isActive) {
+		return;
+	}
+
+	self.isActive = NO;
+
+	printf("******** stop animation!\n");
+
+	if (self.useCADisplayLink) {
+		[self.displayLink invalidate];
+		self.displayLink = nil;
+	} else {
+		[self.animationTimer invalidate];
+		self.animationTimer = nil;
+	}
+
+	[self clearTouches];
+}
+
+- (void)startRendering {
+	if (self.isActive) {
+		return;
+	}
+
+	self.isActive = YES;
+
+	printf("start animation!\n");
+
+	if (self.useCADisplayLink) {
+		self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
+
+		//        if (@available(iOS 10, *)) {
+		self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
+		//        } else {
+		//            // Approximate frame rate
+		//            // assumes device refreshes at 60 fps
+		//            int frameInterval = (int)floor(self.renderingInterval * 60.0f);
+		//            [self.displayLink setFrameInterval:frameInterval];
+		//        }
+
+		// Setup DisplayLink in main thread
+		[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+	} else {
+		self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
+	}
+}
+
+- (void)drawView {
+	if (!self.isActive) {
+		printf("draw view not active!\n");
+		return;
+	}
+
+	if (self.useCADisplayLink) {
+		// Pause the CADisplayLink to avoid recursion
+		[self.displayLink setPaused:YES];
+
+		// Process all input events
+		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
+			;
+
+		// We are good to go, resume the CADisplayLink
+		[self.displayLink setPaused:NO];
+	}
+
+	[self.renderingLayer renderDisplayLayer];
+
+	if (!self.renderer) {
+		return;
+	}
+
+	if ([self.renderer setupView:self]) {
+		return;
+	}
+
+	[self handleMotion];
+	[self.renderer renderOnView:self];
+}
+
+- (BOOL)canRender {
+	if (self.useCADisplayLink) {
+		return self.displayLink != nil;
+	} else {
+		return self.animationTimer != nil;
+	}
+}
+
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+	_renderingInterval = renderingInterval;
+
+	if (self.canRender) {
+		[self stopRendering];
+		[self startRendering];
+	}
+}
+
+- (void)layoutSubviews {
+	if (self.renderingLayer) {
+		self.renderingLayer.frame = self.bounds;
+		[self.renderingLayer layoutDisplayLayer];
+
+		if (DisplayServerIPhone::get_singleton()) {
+			DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
+		}
+	}
+
+	[super layoutSubviews];
+}
+
+// MARK: - Input
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+	return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing {
+	keyboard_text = p_existing;
+	return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+	keyboard_text = String();
+	return [super resignFirstResponder];
+}
+
+- (void)deleteBackward {
+	if (keyboard_text.length()) {
+		keyboard_text.erase(keyboard_text.length() - 1, 1);
+	}
+	DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+}
+
+- (BOOL)hasText {
+	return keyboard_text.length() > 0;
+}
+
+- (void)insertText:(NSString *)p_text {
+	String character;
+	character.parse_utf8([p_text UTF8String]);
+	keyboard_text = keyboard_text + character;
+	DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
+}
+
+// MARK: Touches
+
+- (void)initTouches {
+	for (int i = 0; i < max_touches; i++) {
+		godot_touches[i] = NULL;
+	}
+}
+
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+	int first = -1;
+	for (int i = 0; i < max_touches; i++) {
+		if (first == -1 && godot_touches[i] == NULL) {
+			first = i;
+			continue;
+		}
+		if (godot_touches[i] == p_touch) {
+			return i;
+		}
+	}
+
+	if (first != -1) {
+		godot_touches[first] = p_touch;
+		return first;
+	}
+
+	return -1;
+}
+
+- (int)removeTouch:(UITouch *)p_touch {
+	int remaining = 0;
+	for (int i = 0; i < max_touches; i++) {
+		if (godot_touches[i] == NULL) {
+			continue;
+		}
+		if (godot_touches[i] == p_touch) {
+			godot_touches[i] = NULL;
+		} else {
+			++remaining;
+		}
+	}
+	return remaining;
+}
+
+- (void)clearTouches {
+	for (int i = 0; i < max_touches; i++) {
+		godot_touches[i] = NULL;
+	}
+}
+
+- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			if (touch.phase != UITouchPhaseBegan) {
+				continue;
+			}
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			CGPoint touchPoint = [touch locationInView:self];
+			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+		}
+	}
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			if (touch.phase != UITouchPhaseMoved) {
+				continue;
+			}
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			CGPoint touchPoint = [touch locationInView:self];
+			CGPoint prev_point = [touch previousLocationInView:self];
+			DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+		}
+	}
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			if (touch.phase != UITouchPhaseEnded) {
+				continue;
+			}
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			[self removeTouch:touch];
+			CGPoint touchPoint = [touch locationInView:self];
+			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+		}
+	}
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+	NSArray *tlist = [event.allTouches allObjects];
+	for (unsigned int i = 0; i < [tlist count]; i++) {
+		if ([touches containsObject:[tlist objectAtIndex:i]]) {
+			UITouch *touch = [tlist objectAtIndex:i];
+			if (touch.phase != UITouchPhaseCancelled) {
+				continue;
+			}
+			int tid = [self getTouchIDForTouch:touch];
+			ERR_FAIL_COND(tid == -1);
+			DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
+		}
+	}
+	[self clearTouches];
+}
+
+// MARK: Motion
+
+- (void)handleMotion {
+	if (!self.motionManager) {
+		return;
+	}
+
+	// Just using polling approach for now, we can set this up so it sends
+	// data to us in intervals, might be better. See Apple reference pages
+	// for more details:
+	// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
+
+	// Apple splits our accelerometer date into a gravity and user movement
+	// component. We add them back together
+	CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+	CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
+
+	///@TODO We don't seem to be getting data here, is my device broken or
+	/// is this code incorrect?
+	CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
+
+	///@TODO we can access rotationRate as a CMRotationRate variable
+	///(processed date) or CMGyroData (raw data), have to see what works
+	/// best
+	CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
+
+	// Adjust for screen orientation.
+	// [[UIDevice currentDevice] orientation] changes even if we've fixed
+	// our orientation which is not a good thing when you're trying to get
+	// your user to move the screen in all directions and want consistent
+	// output
+
+	///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+	/// is a bit of a hack. Godot obviously knows the orientation so maybe
+	/// we
+	// can use that instead? (note that left and right seem swapped)
+
+	UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+
+	if (@available(iOS 13, *)) {
+		interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+	} else {
+		interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+	}
+
+	switch (interfaceOrientation) {
+		case UIInterfaceOrientationLandscapeLeft: {
+			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+		} break;
+		case UIInterfaceOrientationLandscapeRight: {
+			DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+			DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+		} break;
+		case UIInterfaceOrientationPortraitUpsideDown: {
+			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+		} break;
+		default: { // assume portrait
+			DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+			DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+		} break;
+	}
+}
+
+@end

+ 44 - 0
platform/iphone/godot_view_renderer.h

@@ -0,0 +1,44 @@
+/*************************************************************************/
+/*  godot_view_renderer.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 <UIKit/UIKit.h>
+
+@protocol GodotViewRendererProtocol <NSObject>
+
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
+
+@end
+
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end

+ 146 - 0
platform/iphone/godot_view_renderer.mm

@@ -0,0 +1,146 @@
+/*************************************************************************/
+/*  godot_view_renderer.mm                                               */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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_renderer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#import "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedLocaleSetup;
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+	if (self.hasFinishedSetup) {
+		return NO;
+	}
+
+	if (!self.hasFinishedLocaleSetup) {
+		[self setupLocaleAndUUID];
+		return YES;
+	}
+
+	if (!self.hasFinishedProjectDataSetup) {
+		[self setupProjectData];
+		return YES;
+	}
+
+	if (!self.hasStartedMain) {
+		self.hasStartedMain = YES;
+		OSIPhone::get_singleton()->start();
+		return YES;
+	}
+
+	self.hasFinishedSetup = YES;
+
+	return NO;
+}
+
+- (void)setupLocaleAndUUID {
+	self.hasFinishedLocaleSetup = YES;
+
+	if (!OS::get_singleton()) {
+		exit(0);
+	}
+
+	NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
+	OSIPhone::get_singleton()->set_locale(String::utf8([locale_code UTF8String]));
+
+	NSString *uuid;
+	if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
+		uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+	} else {
+		// before iOS 6, so just generate an identifier and store it
+		uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"];
+		if (!uuid) {
+			CFUUIDRef cfuuid = CFUUIDCreate(NULL);
+			uuid = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease];
+			CFRelease(cfuuid);
+			[[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
+		}
+	}
+
+	OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
+}
+
+- (void)setupProjectData {
+	self.hasFinishedProjectDataSetup = YES;
+
+	Main::setup2();
+
+	// this might be necessary before here
+	NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+	for (NSString *key in dict) {
+		NSObject *value = [dict objectForKey:key];
+		String ukey = String::utf8([key UTF8String]);
+
+		// we need a NSObject to Variant conversor
+
+		if ([value isKindOfClass:[NSString class]]) {
+			NSString *str = (NSString *)value;
+			String uval = String::utf8([str UTF8String]);
+
+			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+		} else if ([value isKindOfClass:[NSNumber class]]) {
+			NSNumber *n = (NSNumber *)value;
+			double dval = [n doubleValue];
+
+			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+		};
+		// do stuff
+	}
+}
+
+- (void)renderOnView:(UIView *)view {
+	if (!OSIPhone::get_singleton()) {
+		return;
+	}
+
+	OSIPhone::get_singleton()->iterate();
+}
+
+@end

+ 3 - 3
platform/iphone/icloud.h

@@ -44,9 +44,9 @@ class ICloud : public Object {
 	List<Variant> pending_events;
 
 public:
-	Error remove_key(Variant p_param);
-	Variant set_key_values(Variant p_param);
-	Variant get_key_value(Variant p_param);
+	Error remove_key(String p_param);
+	Array set_key_values(Dictionary p_params);
+	Variant get_key_value(String p_param);
 	Error synchronize_key_values();
 	Variant get_all_key_values();
 

+ 14 - 16
platform/iphone/icloud.mm

@@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;
 
 void ICloud::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
+
 	ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
 	ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
+
 	ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
 	ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
 
@@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {
 	} else if ([object isKindOfClass:[NSArray class]]) {
 		Array result;
 		NSArray *array = (NSArray *)object;
-		for (unsigned int i = 0; i < [array count]; ++i) {
+		for (NSUInteger i = 0; i < [array count]; ++i) {
 			NSObject *value = [array objectAtIndex:i];
 			result.push_back(nsobject_to_variant(value));
 		}
@@ -149,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {
 NSObject *variant_to_nsobject(Variant v) {
 	if (v.get_type() == Variant::STRING) {
 		return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
-	} else if (v.get_type() == Variant::REAL) {
+	} else if (v.get_type() == Variant::FLOAT) {
 		return [NSNumber numberWithDouble:(double)v];
 	} else if (v.get_type() == Variant::INT) {
 		return [NSNumber numberWithLongLong:(long)(int)v];
@@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {
 		NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
 		Dictionary dic = v;
 		Array keys = dic.keys();
-		for (unsigned int i = 0; i < keys.size(); ++i) {
+		for (int i = 0; i < keys.size(); ++i) {
 			NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
 			NSObject *value = variant_to_nsobject(dic[keys[i]]);
 
@@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {
 	} else if (v.get_type() == Variant::ARRAY) {
 		NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
 		Array arr = v;
-		for (unsigned int i = 0; i < arr.size(); ++i) {
+		for (int i = 0; i < arr.size(); ++i) {
 			NSObject *value = variant_to_nsobject(arr[i]);
 			if (value == NULL) {
 				//trying to add something unsupported to the array. cancel the whole array
@@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {
 	return NULL;
 }
 
-Error ICloud::remove_key(Variant p_param) {
-	String param = p_param;
-	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Error ICloud::remove_key(String p_param) {
+	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
 
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 
@@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {
 }
 
 //return an array of the keys that could not be set
-Variant ICloud::set_key_values(Variant p_params) {
-	Dictionary params = p_params;
-	Array keys = params.keys();
+Array ICloud::set_key_values(Dictionary p_params) {
+	Array keys = p_params.keys();
 
 	Array error_keys;
 
-	for (unsigned int i = 0; i < keys.size(); ++i) {
+	for (int i = 0; i < keys.size(); ++i) {
 		String variant_key = keys[i];
-		Variant variant_value = params[variant_key];
+		Variant variant_value = p_params[variant_key];
 
 		NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
 		if (key == NULL) {
@@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {
 	return error_keys;
 }
 
-Variant ICloud::get_key_value(Variant p_param) {
-	String param = p_param;
-
-	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Variant ICloud::get_key_value(String p_param) {
+	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
 	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
 
 	if (![[store dictionaryRepresentation] objectForKey:key]) {

+ 2 - 2
platform/iphone/in_app_store.h

@@ -44,9 +44,9 @@ class InAppStore : public Object {
 	List<Variant> pending_events;
 
 public:
-	Error request_product_info(Variant p_params);
+	Error request_product_info(Dictionary p_params);
 	Error restore_purchases();
-	Error purchase(Variant p_params);
+	Error purchase(Dictionary p_params);
 
 	int get_pending_event_count();
 	Variant pop_pending_event();

+ 29 - 11
platform/iphone/in_app_store.mm

@@ -39,8 +39,10 @@ extern "C" {
 
 bool auto_finish_transactions = true;
 NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
+static NSArray *latestProducts;
 
 @interface SKProduct (LocalizedPrice)
+
 @property(nonatomic, readonly) NSString *localizedPrice;
 @end
 
@@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {
 
 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
 	NSArray *products = response.products;
+	latestProducts = products;
+
 	Dictionary ret;
 	ret["type"] = "product_info";
 	ret["result"] = "ok";
@@ -126,11 +130,10 @@ void InAppStore::_bind_methods() {
 
 @end
 
-Error InAppStore::request_product_info(Variant p_params) {
-	Dictionary params = p_params;
-	ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
+Error InAppStore::request_product_info(Dictionary p_params) {
+	ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
 
-	PackedStringArray pids = params["product_ids"];
+	PackedStringArray pids = p_params["product_ids"];
 	printf("************ request product info! %i\n", pids.size());
 
 	NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
@@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {
 						// which is still available in iOS 7.
 
 						// Use SKPaymentTransaction's transactionReceipt.
-						receipt = transaction.transactionReceipt;
+						receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
 					}
 
 				} else {
-					receipt = transaction.transactionReceipt;
+					receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
 				}
 
 				NSString *receipt_to_send = nil;
@@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {
 
 @end
 
-Error InAppStore::purchase(Variant p_params) {
+Error InAppStore::purchase(Dictionary p_params) {
 	ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
 	if (![SKPaymentQueue canMakePayments])
 		return ERR_UNAVAILABLE;
 
 	printf("purchasing!\n");
-	Dictionary params = p_params;
-	ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
+	ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
+
+	NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
+
+	SKProduct *product = nil;
+
+	if (latestProducts) {
+		for (SKProduct *storedProduct in latestProducts) {
+			if ([storedProduct.productIdentifier isEqualToString:pid]) {
+				product = storedProduct;
+				break;
+			}
+		}
+	}
+
+	if (!product) {
+		return ERR_INVALID_PARAMETER;
+	}
 
-	NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
-	SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
+	SKPayment *payment = [SKPayment paymentWithProduct:product];
 	SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
 	[defq addPayment:payment];
 	printf("purchase sent!\n");

+ 14 - 4
platform/iphone/ios.mm

@@ -29,17 +29,27 @@
 /*************************************************************************/
 
 #include "ios.h"
-#include <sys/sysctl.h>
-
+#import "app_delegate.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);
 };
 
 void iOS::alert(const char *p_alert, const char *p_title) {
-	UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
-	[alert show];
+	NSString *title = [NSString stringWithUTF8String:p_title];
+	NSString *message = [NSString stringWithUTF8String:p_alert];
+
+	UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+	UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+													 style:UIAlertActionStyleCancel
+												   handler:^(id){
+												   }];
+
+	[alert addAction:button];
+
+	[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
 }
 
 String iOS::get_model() const {

+ 50 - 0
platform/iphone/joypad_iphone.h

@@ -0,0 +1,50 @@
+/*************************************************************************/
+/*  joypad_iphone.h                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 <GameController/GameController.h>
+
+@interface JoypadIPhoneObserver : NSObject
+
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
+
+@end
+
+class JoypadIPhone {
+private:
+	JoypadIPhoneObserver *observer;
+
+public:
+	JoypadIPhone();
+	~JoypadIPhone();
+
+	void start_processing();
+};

+ 380 - 0
platform/iphone/joypad_iphone.mm

@@ -0,0 +1,380 @@
+/*************************************************************************/
+/*  joypad_iphone.mm                                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 "joypad_iphone.h"
+#include "core/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "main/main.h"
+
+#import "godot_view.h"
+
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+	observer = [[JoypadIPhoneObserver alloc] init];
+	[observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+	if (observer) {
+		[observer finishObserving];
+		observer = nil;
+	}
+}
+
+void JoypadIPhone::start_processing() {
+	if (observer) {
+		[observer startProcessing];
+	}
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+	self = [super init];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (void)godot_commonInit {
+	self.isObserving = NO;
+	self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+	self.isProcessing = YES;
+
+	for (GCController *controller in self.joypadsQueue) {
+		[self addiOSJoypad:controller];
+	}
+
+	[self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+	if (self.isObserving) {
+		return;
+	}
+
+	self.isObserving = YES;
+
+	self.connectedJoypads = [NSMutableDictionary dictionary];
+	self.joypadsQueue = [NSMutableArray array];
+
+	// get told when controllers connect, this will be called right away for
+	// already connected controllers
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(controllerWasConnected:)
+				   name:GCControllerDidConnectNotification
+				 object:nil];
+
+	// get told when controllers disconnect
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(controllerWasDisconnected:)
+				   name:GCControllerDidDisconnectNotification
+				 object:nil];
+}
+
+- (void)finishObserving {
+	if (self.isObserving) {
+		[[NSNotificationCenter defaultCenter] removeObserver:self];
+	}
+
+	self.isObserving = NO;
+	self.isProcessing = NO;
+
+	self.connectedJoypads = nil;
+	self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+	[self finishObserving];
+
+	[super dealloc];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+	NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+	for (NSNumber *key in keys) {
+		int joy_id = [key intValue];
+		return joy_id;
+	};
+
+	return -1;
+};
+
+- (void)addiOSJoypad:(GCController *)controller {
+	//     get a new id for our controller
+	int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+	if (joy_id == -1) {
+		printf("Couldn't retrieve new joy id\n");
+		return;
+	}
+
+	// assign our player index
+	if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+		controller.playerIndex = [self getFreePlayerIndex];
+	};
+
+	// tell Godot about our new controller
+	Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
+
+	// add it to our dictionary, this will retain our controllers
+	[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+	// set our input handler
+	[self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+	// get our controller
+	GCController *controller = (GCController *)notification.object;
+
+	if (!controller) {
+		printf("Couldn't retrieve new controller\n");
+		return;
+	}
+
+	if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+		printf("Controller is already registered\n");
+	} else if (!self.isProcessing) {
+		[self.joypadsQueue addObject:controller];
+	} else {
+		[self addiOSJoypad:controller];
+	}
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+	// find our joystick, there should be only one in our dictionary
+	GCController *controller = (GCController *)notification.object;
+
+	if (!controller) {
+		return;
+	}
+
+	NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+	for (NSNumber *key in keys) {
+		// tell Godot this joystick is no longer there
+		int joy_id = [key intValue];
+		Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+		// and remove it from our dictionary
+		[self.connectedJoypads removeObjectForKey:key];
+	};
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+	bool have_player_1 = false;
+	bool have_player_2 = false;
+	bool have_player_3 = false;
+	bool have_player_4 = false;
+
+	if (self.connectedJoypads == nil) {
+		NSArray *keys = [self.connectedJoypads allKeys];
+		for (NSNumber *key in keys) {
+			GCController *controller = [self.connectedJoypads objectForKey:key];
+			if (controller.playerIndex == GCControllerPlayerIndex1) {
+				have_player_1 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex2) {
+				have_player_2 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex3) {
+				have_player_3 = true;
+			} else if (controller.playerIndex == GCControllerPlayerIndex4) {
+				have_player_4 = true;
+			};
+		};
+	};
+
+	if (!have_player_1) {
+		return GCControllerPlayerIndex1;
+	} else if (!have_player_2) {
+		return GCControllerPlayerIndex2;
+	} else if (!have_player_3) {
+		return GCControllerPlayerIndex3;
+	} else if (!have_player_4) {
+		return GCControllerPlayerIndex4;
+	} else {
+		return GCControllerPlayerIndexUnset;
+	};
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+	// Hook in the callback handler for the correct gamepad profile.
+	// This is a bit of a weird design choice on Apples part.
+	// You need to select the most capable gamepad profile for the
+	// gamepad attached.
+	if (controller.extendedGamepad != nil) {
+		// The extended gamepad profile has all the input you could possibly find on
+		// a gamepad but will only be active if your gamepad actually has all of
+		// these...
+		controller.extendedGamepad.valueChangedHandler = ^(
+				GCExtendedGamepad *gamepad, GCControllerElement *element) {
+			int joy_id = [self getJoyIdForController:controller];
+
+			if (element == gamepad.buttonA) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+						gamepad.buttonA.isPressed);
+			} else if (element == gamepad.buttonB) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+						gamepad.buttonB.isPressed);
+			} else if (element == gamepad.buttonX) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+						gamepad.buttonX.isPressed);
+			} else if (element == gamepad.buttonY) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+						gamepad.buttonY.isPressed);
+			} else if (element == gamepad.leftShoulder) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+						gamepad.leftShoulder.isPressed);
+			} else if (element == gamepad.rightShoulder) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+						gamepad.rightShoulder.isPressed);
+			} else if (element == gamepad.dpad) {
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+						gamepad.dpad.up.isPressed);
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+						gamepad.dpad.down.isPressed);
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+						gamepad.dpad.left.isPressed);
+				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+						gamepad.dpad.right.isPressed);
+			};
+
+			Input::JoyAxis jx;
+			jx.min = -1;
+			if (element == gamepad.leftThumbstick) {
+				jx.value = gamepad.leftThumbstick.xAxis.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
+				jx.value = -gamepad.leftThumbstick.yAxis.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
+			} else if (element == gamepad.rightThumbstick) {
+				jx.value = gamepad.rightThumbstick.xAxis.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
+				jx.value = -gamepad.rightThumbstick.yAxis.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
+			} else if (element == gamepad.leftTrigger) {
+				jx.value = gamepad.leftTrigger.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
+			} else if (element == gamepad.rightTrigger) {
+				jx.value = gamepad.rightTrigger.value;
+				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
+			};
+		};
+	}
+	//    else if (controller.gamepad != nil) {
+	//        // gamepad is the standard profile with 4 buttons, shoulder buttons and a
+	//        // D-pad
+	//        controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
+	//                GCControllerElement *element) {
+	//            int joy_id = [self getJoyIdForController:controller];
+	//
+	//            if (element == gamepad.buttonA) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+	//                        gamepad.buttonA.isPressed);
+	//            } else if (element == gamepad.buttonB) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+	//                        gamepad.buttonB.isPressed);
+	//            } else if (element == gamepad.buttonX) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+	//                        gamepad.buttonX.isPressed);
+	//            } else if (element == gamepad.buttonY) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+	//                        gamepad.buttonY.isPressed);
+	//            } else if (element == gamepad.leftShoulder) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+	//                        gamepad.leftShoulder.isPressed);
+	//            } else if (element == gamepad.rightShoulder) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+	//                        gamepad.rightShoulder.isPressed);
+	//            } else if (element == gamepad.dpad) {
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+	//                        gamepad.dpad.up.isPressed);
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+	//                        gamepad.dpad.down.isPressed);
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+	//                        gamepad.dpad.left.isPressed);
+	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+	//                        gamepad.dpad.right.isPressed);
+	//            };
+	//        };
+	//#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
+	//        // while we are setting that as the minimum, seems our
+	//        // build environment doesn't like it
+	//    } else if (controller.microGamepad != nil) {
+	//        // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+	//        controller.microGamepad.valueChangedHandler =
+	//                ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+	//                    int joy_id = [self getJoyIdForController:controller];
+	//
+	//                    if (element == gamepad.buttonA) {
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+	//                                gamepad.buttonA.isPressed);
+	//                    } else if (element == gamepad.buttonX) {
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+	//                                gamepad.buttonX.isPressed);
+	//                    } else if (element == gamepad.dpad) {
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+	//                                gamepad.dpad.up.isPressed);
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+	//                                gamepad.dpad.down.isPressed);
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+	//                                gamepad.dpad.left.isPressed);
+	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+	//                                gamepad.dpad.right.isPressed);
+	//                    };
+	//                };
+	//#endif
+	//    };
+
+	///@TODO need to add support for controller.motion which gives us access to
+	/// the orientation of the device (if supported)
+
+	///@TODO need to add support for controllerPausedHandler which should be a
+	/// toggle
+};
+
+@end

+ 10 - 5
platform/iphone/main.m

@@ -32,20 +32,25 @@
 
 #import <UIKit/UIKit.h>
 #include <stdio.h>
+#include <vulkan/vulkan.h>
 
 int gargc;
 char **gargv;
 
 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
+
 	printf("*********** main.m\n");
 	gargc = argc;
 	gargv = argv;
 
-	NSAutoreleasePool *pool = [NSAutoreleasePool new];
-	AppDelegate *app = [AppDelegate alloc];
 	printf("running app main\n");
-	UIApplicationMain(argc, argv, nil, @"AppDelegate");
-	printf("main done, pool release\n");
-	[pool release];
+	@autoreleasepool {
+		UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+	}
+	printf("main done\n");
 	return 0;
 }

+ 0 - 632
platform/iphone/os_iphone.cpp

@@ -1,632 +0,0 @@
-/*************************************************************************/
-/*  os_iphone.cpp                                                        */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* 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.                */
-/*************************************************************************/
-
-#ifdef IPHONE_ENABLED
-
-#include "os_iphone.h"
-
-#if defined(OPENGL_ENABLED)
-#include "drivers/gles2/rasterizer_gles2.h"
-#endif
-
-#if defined(VULKAN_ENABLED)
-#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
-// #import <QuartzCore/CAMetalLayer.h>
-#include <vulkan/vulkan_metal.h>
-#endif
-
-#include "servers/rendering/rendering_server_raster.h"
-#include "servers/rendering/rendering_server_wrap_mt.h"
-
-#include "main/main.h"
-
-#include "core/io/file_access_pack.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
-#include "drivers/unix/syslog_logger.h"
-
-#include "semaphore_iphone.h"
-
-#include <dlfcn.h>
-
-int OSIPhone::get_video_driver_count() const {
-	return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
-	switch (p_driver) {
-		case VIDEO_DRIVER_GLES2:
-			return "GLES2";
-	}
-	ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
-};
-
-OSIPhone *OSIPhone::get_singleton() {
-	return (OSIPhone *)OS::get_singleton();
-};
-
-extern int gl_view_base_fb; // from gl_view.mm
-
-void OSIPhone::set_data_dir(String p_dir) {
-	DirAccess *da = DirAccess::open(p_dir);
-
-	data_dir = da->get_current_dir();
-	printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
-	memdelete(da);
-};
-
-void OSIPhone::set_unique_id(String p_id) {
-	unique_id = p_id;
-};
-
-String OSIPhone::get_unique_id() const {
-	return unique_id;
-};
-
-void OSIPhone::initialize_core() {
-	OS_Unix::initialize_core();
-
-	set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
-	return video_driver_index;
-}
-
-Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-	video_driver_index = p_video_driver;
-
-#if defined(OPENGL_ENABLED)
-	bool gl_initialization_error = false;
-
-	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
-	if (RasterizerGLES2::is_viable() == OK) {
-		RasterizerGLES2::register_config();
-		RasterizerGLES2::make_current();
-	} else {
-		gl_initialization_error = true;
-	}
-
-	if (gl_initialization_error) {
-		OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
-				"Unable to initialize video driver");
-		return ERR_UNAVAILABLE;
-	}
-#endif
-
-#if defined(VULKAN_ENABLED)
-	RasterizerRD::make_current();
-#endif
-
-	rendering_server = memnew(RenderingServerRaster);
-	// FIXME: Reimplement threaded rendering
-	if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
-		rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
-	}
-	rendering_server->init();
-	//rendering_server->cursor_set_visible(false, 0);
-
-#if defined(OPENGL_ENABLED)
-	// reset this to what it should be, it will have been set to 0 after rendering_server->init() is called
-	RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
-#endif
-
-	AudioDriverManager::initialize(p_audio_driver);
-
-	input = memnew(InputDefault);
-
-#ifdef GAME_CENTER_ENABLED
-	game_center = memnew(GameCenter);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
-	game_center->connect();
-#endif
-
-#ifdef STOREKIT_ENABLED
-	store_kit = memnew(InAppStore);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
-	icloud = memnew(ICloud);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
-	//icloud->connect();
-#endif
-	ios = memnew(iOS);
-	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
-	return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
-	return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
-	main_loop = p_main_loop;
-
-	if (main_loop) {
-		input->set_main_loop(p_main_loop);
-		main_loop->init();
-	}
-};
-
-bool OSIPhone::iterate() {
-	if (!main_loop)
-		return true;
-
-	if (main_loop) {
-		for (int i = 0; i < event_count; i++) {
-			input->parse_input_event(event_queue[i]);
-		};
-	};
-	event_count = 0;
-
-	return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
-	Ref<InputEventKey> ev;
-	ev.instance();
-	ev->set_echo(false);
-	ev->set_pressed(p_pressed);
-	ev->set_keycode(p_key);
-	ev->set_physical_keycode(p_key);
-	ev->set_unicode(p_key);
-	queue_event(ev);
-};
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
-	if (!GLOBAL_DEF("debug/disable_touch", false)) {
-		Ref<InputEventScreenTouch> ev;
-		ev.instance();
-
-		ev->set_index(p_idx);
-		ev->set_pressed(p_pressed);
-		ev->set_position(Vector2(p_x, p_y));
-		queue_event(ev);
-	};
-
-	touch_list.pressed[p_idx] = p_pressed;
-};
-
-void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
-	if (!GLOBAL_DEF("debug/disable_touch", false)) {
-		Ref<InputEventScreenDrag> ev;
-		ev.instance();
-		ev->set_index(p_idx);
-		ev->set_position(Vector2(p_x, p_y));
-		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
-		queue_event(ev);
-	};
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
-	ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
-	event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
-	for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
-		if (touch_list.pressed[i]) {
-			// send a mouse_up outside the screen
-			touch_press(i, -1, -1, false, false);
-		};
-	};
-};
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
-	input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
-	// Found out the Z should not be negated! Pass as is!
-	input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
-	/*
-	if (p_x != last_accel.x) {
-		//printf("updating accel x %f\n", p_x);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_0;
-		ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
-		last_accel.x = p_x;
-		queue_event(ev);
-	};
-	if (p_y != last_accel.y) {
-		//printf("updating accel y %f\n", p_y);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_1;
-		ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
-		last_accel.y = p_y;
-		queue_event(ev);
-	};
-	if (p_z != last_accel.z) {
-		//printf("updating accel z %f\n", p_z);
-		InputEvent ev;
-		ev.type = InputEvent::JOYPAD_MOTION;
-		ev.device = 0;
-		ev.joy_motion.axis = JOY_ANALOG_2;
-		ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
-		last_accel.z = p_z;
-		queue_event(ev);
-	};
-	*/
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
-	input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
-	input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
-	return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
-	input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
-	input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
-	input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
-	if (main_loop) {
-		main_loop->finish();
-		memdelete(main_loop);
-	};
-
-	main_loop = nullptr;
-};
-
-void OSIPhone::finalize() {
-	delete_main_loop();
-
-	memdelete(input);
-	memdelete(ios);
-
-#ifdef GAME_CENTER_ENABLED
-	memdelete(game_center);
-#endif
-
-#ifdef STOREKIT_ENABLED
-	memdelete(store_kit);
-#endif
-
-#ifdef ICLOUD_ENABLED
-	memdelete(icloud);
-#endif
-
-	rendering_server->finish();
-	memdelete(rendering_server);
-	//	memdelete(rasterizer);
-
-	// Free unhandled events before close
-	for (int i = 0; i < MAX_EVENTS; i++) {
-		event_queue[i].unref();
-	};
-	event_count = 0;
-};
-
-void OSIPhone::set_mouse_show(bool p_show) {}
-void OSIPhone::set_mouse_grab(bool p_grab) {}
-
-bool OSIPhone::is_mouse_grab_enabled() const {
-	return true;
-};
-
-Point2 OSIPhone::get_mouse_position() const {
-	return Point2();
-};
-
-int OSIPhone::get_mouse_button_state() const {
-	return 0;
-};
-
-void OSIPhone::set_window_title(const String &p_title) {}
-
-void OSIPhone::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());
-}
-
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
-	if (p_path.length() == 0) {
-		p_library_handle = RTLD_SELF;
-		return OK;
-	}
-	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
-	if (p_library_handle == RTLD_SELF) {
-		return OK;
-	}
-	return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
-void register_dynamic_symbol(char *name, void *address) {
-	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Error OSIPhone::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 = OSIPhone::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);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-	video_mode = p_video_mode;
-};
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
-	return video_mode;
-};
-
-void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-	p_list->push_back(video_mode);
-};
-
-bool OSIPhone::can_draw() const {
-	if (native_video_is_playing())
-		return false;
-	return true;
-};
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
-#if defined(OPENGL_ENABLED)
-	// gl_view_base_fb has not been updated yet
-	RasterizerStorageGLES2::system_fbo = p_fb;
-#endif
-
-	return 0;
-};
-
-bool OSIPhone::has_virtual_keyboard() const {
-	return true;
-};
-
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
-	_show_keyboard(p_existing_text);
-};
-
-void OSIPhone::hide_virtual_keyboard() {
-	_hide_keyboard();
-};
-
-void OSIPhone::set_virtual_keyboard_height(int p_height) {
-	virtual_keyboard_height = p_height;
-}
-
-int OSIPhone::get_virtual_keyboard_height() const {
-	return virtual_keyboard_height;
-}
-
-Error OSIPhone::shell_open(String p_uri) {
-	return _shell_open(p_uri);
-};
-
-void OSIPhone::set_keep_screen_on(bool p_enabled) {
-	OS::set_keep_screen_on(p_enabled);
-	_set_keep_screen_on(p_enabled);
-};
-
-String OSIPhone::get_user_data_dir() const {
-	return data_dir;
-};
-
-String OSIPhone::get_name() const {
-	return "iOS";
-};
-
-String OSIPhone::get_model_name() const {
-	String model = ios->get_model();
-	if (model != "")
-		return model;
-
-	return OS_Unix::get_model_name();
-}
-
-Size2 OSIPhone::get_window_size() const {
-	return Vector2(video_mode.width, video_mode.height);
-}
-
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
-
-Rect2 OSIPhone::get_window_safe_area() const {
-	return _get_ios_window_safe_area(video_mode.width, video_mode.height);
-}
-
-bool OSIPhone::has_touchscreen_ui_hint() const {
-	return true;
-}
-
-void OSIPhone::set_locale(String p_locale) {
-	locale_code = p_locale;
-}
-
-String OSIPhone::get_locale() const {
-	return locale_code;
-}
-
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
-Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
-	FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
-	bool exists = f && f->is_open();
-
-	String tempFile = get_user_data_dir();
-	if (!exists)
-		return FAILED;
-
-	if (p_path.begins_with("res://")) {
-		if (PackedData::get_singleton()->has_path(p_path)) {
-			print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
-			return ERR_INVALID_PARAMETER;
-		} else {
-			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
-		}
-	} else if (p_path.begins_with("user://"))
-		p_path = p_path.replace("user:/", get_user_data_dir());
-
-	memdelete(f);
-
-	print("Playing video: %S\n", p_path.c_str());
-	if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
-		return OK;
-	return FAILED;
-}
-
-bool OSIPhone::native_video_is_playing() const {
-	return _is_video_playing();
-}
-
-void OSIPhone::native_video_pause() {
-	if (native_video_is_playing())
-		_pause_video();
-}
-
-void OSIPhone::native_video_unpause() {
-	_unpause_video();
-};
-
-void OSIPhone::native_video_focus_out() {
-	_focus_out_video();
-};
-
-void OSIPhone::native_video_stop() {
-	if (native_video_is_playing())
-		_stop_video();
-}
-
-void OSIPhone::vibrate_handheld(int p_duration_ms) {
-	// iOS does not support duration for vibration
-	_vibrate();
-}
-
-bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
-	return p_feature == "mobile";
-}
-
-// 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;
-
-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) * 32);
-		if (new_ptr) {
-			ios_init_callbacks = (init_callback *)(new_ptr);
-			ios_init_callbacks_capacity += 32;
-		}
-	}
-	if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
-		ios_init_callbacks[ios_init_callbacks_count] = cb;
-		++ios_init_callbacks_count;
-	}
-}
-
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
-	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;
-	rendering_server = nullptr;
-
-	VideoMode vm;
-	vm.fullscreen = true;
-	vm.width = width;
-	vm.height = height;
-	vm.resizable = false;
-	set_video_mode(vm);
-	event_count = 0;
-	virtual_keyboard_height = 0;
-
-	// can't call set_data_dir from here, since it requires DirAccess
-	// which is initialized in initialize_core
-	data_dir = p_data_dir;
-
-	Vector<Logger *> loggers;
-	loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
-	// it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
-	loggers.push_back(memnew(StdLogger));
-#endif
-	_set_logger(memnew(CompositeLogger(loggers)));
-
-	AudioDriverManager::add_driver(&audio_driver);
-};
-
-OSIPhone::~OSIPhone() {
-}
-
-#endif

+ 38 - 113
platform/iphone/os_iphone.h

@@ -33,16 +33,15 @@
 #ifndef OS_IPHONE_H
 #define OS_IPHONE_H
 
-#include "core/input/input.h"
 #include "drivers/coreaudio/audio_driver_coreaudio.h"
 #include "drivers/unix/os_unix.h"
 #include "game_center.h"
 #include "icloud.h"
 #include "in_app_store.h"
 #include "ios.h"
+#include "joypad_iphone.h"
 #include "servers/audio_server.h"
 #include "servers/rendering/rasterizer.h"
-#include "servers/rendering_server.h"
 
 #if defined(VULKAN_ENABLED)
 #include "drivers/vulkan/rendering_device_vulkan.h"
@@ -51,16 +50,9 @@
 
 class OSIPhone : public OS_Unix {
 private:
-	enum {
-		MAX_MOUSE_COUNT = 8,
-		MAX_EVENTS = 64,
-	};
-
 	static HashMap<String, void *> dynamic_symbol_lookup_table;
 	friend void register_dynamic_symbol(char *name, void *address);
 
-	RenderingServer *rendering_server;
-
 	AudioDriverCoreAudio audio_driver;
 
 #ifdef GAME_CENTER_ENABLED
@@ -74,139 +66,72 @@ private:
 #endif
 	iOS *ios;
 
-	MainLoop *main_loop;
-
-#if defined(VULKAN_ENABLED)
-	VulkanContextIPhone *context_vulkan;
-	RenderingDeviceVulkan *rendering_device_vulkan;
-#endif
-	VideoMode video_mode;
-
-	virtual int get_video_driver_count() const;
-	virtual const char *get_video_driver_name(int p_driver) const;
+	JoypadIPhone *joypad_iphone;
 
-	virtual int get_current_video_driver() const;
-
-	virtual void initialize_core();
-	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
-	virtual void set_main_loop(MainLoop *p_main_loop);
-	virtual MainLoop *get_main_loop() const;
-
-	virtual void delete_main_loop();
+	MainLoop *main_loop;
 
-	virtual void finalize();
+	virtual void initialize_core() override;
+	virtual void initialize() override;
 
-	struct MouseList {
-		bool pressed[MAX_MOUSE_COUNT];
-		MouseList() {
-			for (int i = 0; i < MAX_MOUSE_COUNT; i++)
-				pressed[i] = false;
-		};
-	};
+	virtual void initialize_joypads() override {
+	}
 
-	MouseList touch_list;
+	virtual void set_main_loop(MainLoop *p_main_loop) override;
+	virtual MainLoop *get_main_loop() const override;
 
-	Vector3 last_accel;
+	virtual void delete_main_loop() override;
 
-	Ref<InputEvent> event_queue[MAX_EVENTS];
-	int event_count;
-	void queue_event(const Ref<InputEvent> &p_event);
+	virtual void finalize() override;
 
-	String data_dir;
+	String user_data_dir;
 	String unique_id;
 	String locale_code;
 
-	InputDefault *input;
+	bool is_focused = false;
 
-	int virtual_keyboard_height;
-
-	int video_driver_index;
+	void deinitialize_modules();
 
 public:
-	bool iterate();
-
-	uint8_t get_orientations() const;
-
-	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
-	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
-	void touches_cancelled();
-	void key(uint32_t p_key, bool p_pressed);
-	void set_virtual_keyboard_height(int p_height);
-
-	int set_base_framebuffer(int p_fb);
-
-	void update_gravity(float p_x, float p_y, float p_z);
-	void update_accelerometer(float p_x, float p_y, float p_z);
-	void update_magnetometer(float p_x, float p_y, float p_z);
-	void update_gyroscope(float p_x, float p_y, float p_z);
-
-	int get_unused_joy_id();
-	void joy_connection_changed(int p_idx, bool p_connected, String p_name);
-	void joy_button(int p_device, int p_button, bool p_pressed);
-	void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
-
 	static OSIPhone *get_singleton();
 
-	virtual void set_mouse_show(bool p_show);
-	virtual void set_mouse_grab(bool p_grab);
-	virtual bool is_mouse_grab_enabled() const;
-	virtual Point2 get_mouse_position() const;
-	virtual int get_mouse_button_state() const;
-	virtual void set_window_title(const String &p_title);
-
-	virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+	OSIPhone(String p_data_dir);
+	~OSIPhone();
 
-	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
-	virtual Error close_dynamic_library(void *p_library_handle);
-	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+	void initialize_modules();
 
-	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
-	virtual VideoMode get_video_mode(int p_screen = 0) const;
-	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
+	bool iterate();
 
-	virtual void set_keep_screen_on(bool p_enabled);
+	void start();
 
-	virtual bool can_draw() const;
+	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) 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 bool has_virtual_keyboard() const;
-	virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
-	virtual void hide_virtual_keyboard();
-	virtual int get_virtual_keyboard_height() const;
+	virtual void alert(const String &p_alert,
+			const String &p_title = "ALERT!") override;
 
-	virtual Size2 get_window_size() const;
-	virtual Rect2 get_window_safe_area() const;
+	virtual String get_name() const override;
+	virtual String get_model_name() const override;
 
-	virtual bool has_touchscreen_ui_hint() const;
+	virtual Error shell_open(String p_uri) override;
 
-	void set_data_dir(String p_dir);
+	void set_user_data_dir(String p_dir);
+	virtual String get_user_data_dir() const override;
 
-	virtual String get_name() const;
-	virtual String get_model_name() const;
+	void set_locale(String p_locale);
+	virtual String get_locale() const override;
 
-	Error shell_open(String p_uri);
+	void set_unique_id(String p_id);
+	virtual String get_unique_id() const override;
 
-	String get_user_data_dir() const;
+	virtual void vibrate_handheld(int p_duration_ms = 500) override;
 
-	void set_locale(String p_locale);
-	String get_locale() const;
+	virtual bool _check_internal_feature_support(const String &p_feature) override;
 
-	void set_unique_id(String p_id);
-	String get_unique_id() const;
-
-	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-	virtual bool native_video_is_playing() const;
-	virtual void native_video_pause();
-	virtual void native_video_unpause();
-	virtual void native_video_focus_out();
-	virtual void native_video_stop();
-	virtual void vibrate_handheld(int p_duration_ms = 500);
-
-	virtual bool _check_internal_feature_support(const String &p_feature);
-	OSIPhone(int width, int height, String p_data_dir);
-	~OSIPhone();
+	void on_focus_out();
+	void on_focus_in();
 };
 
 #endif // OS_IPHONE_H
 
-#endif
+#endif // IPHONE_ENABLED

+ 369 - 0
platform/iphone/os_iphone.mm

@@ -0,0 +1,369 @@
+/*************************************************************************/
+/*  os_iphone.mm                                                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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.                */
+/*************************************************************************/
+
+#ifdef IPHONE_ENABLED
+
+#include "os_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#include "main/main.h"
+#import "view_controller.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+
+#if defined(OPENGL_ENABLED)
+#include "drivers/gles2/rasterizer_gles2.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#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 *> OSIPhone::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) * 32);
+		if (new_ptr) {
+			ios_init_callbacks = (init_callback *)(new_ptr);
+			ios_init_callbacks_capacity += 32;
+		}
+	}
+	if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+		ios_init_callbacks[ios_init_callbacks_count] = cb;
+		++ios_init_callbacks_count;
+	}
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSIPhone *OSIPhone::get_singleton() {
+	return (OSIPhone *)OS::get_singleton();
+}
+
+OSIPhone::OSIPhone(String p_data_dir) {
+	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;
+
+	// can't call set_data_dir from here, since it requires DirAccess
+	// which is initialized in initialize_core
+	user_data_dir = p_data_dir;
+
+	Vector<Logger *> loggers;
+	loggers.push_back(memnew(SyslogLogger));
+#ifdef DEBUG_ENABLED
+	// it seems iOS app's stdout/stderr is only obtainable if you launch it from
+	// Xcode
+	loggers.push_back(memnew(StdLogger));
+#endif
+	_set_logger(memnew(CompositeLogger(loggers)));
+
+	AudioDriverManager::add_driver(&audio_driver);
+
+	DisplayServerIPhone::register_iphone_driver();
+}
+
+OSIPhone::~OSIPhone() {}
+
+void OSIPhone::initialize_core() {
+	OS_Unix::initialize_core();
+
+	set_user_data_dir(user_data_dir);
+}
+
+void OSIPhone::initialize() {
+	initialize_core();
+}
+
+void OSIPhone::initialize_modules() {
+#ifdef GAME_CENTER_ENABLED
+	game_center = memnew(GameCenter);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
+	game_center->connect();
+#endif
+
+#ifdef STOREKIT_ENABLED
+	store_kit = memnew(InAppStore);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
+#endif
+
+#ifdef ICLOUD_ENABLED
+	icloud = memnew(ICloud);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
+#endif
+
+	ios = memnew(iOS);
+	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
+
+	joypad_iphone = memnew(JoypadIPhone);
+}
+
+void OSIPhone::deinitialize_modules() {
+	if (joypad_iphone) {
+		memdelete(joypad_iphone);
+	}
+
+	if (ios) {
+		memdelete(ios);
+	}
+
+#ifdef GAME_CENTER_ENABLED
+	if (game_center) {
+		memdelete(game_center);
+	}
+#endif
+
+#ifdef STOREKIT_ENABLED
+	if (store_kit) {
+		memdelete(store_kit);
+	}
+#endif
+
+#ifdef ICLOUD_ENABLED
+	if (icloud) {
+		memdelete(icloud);
+	}
+#endif
+}
+
+void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+	main_loop = p_main_loop;
+
+	if (main_loop) {
+		main_loop->init();
+	}
+}
+
+MainLoop *OSIPhone::get_main_loop() const {
+	return main_loop;
+}
+
+void OSIPhone::delete_main_loop() {
+	if (main_loop) {
+		main_loop->finish();
+		memdelete(main_loop);
+	};
+
+	main_loop = nullptr;
+}
+
+bool OSIPhone::iterate() {
+	if (!main_loop) {
+		return true;
+	}
+
+	if (DisplayServer::get_singleton()) {
+		DisplayServer::get_singleton()->process_events();
+	}
+
+	return Main::iteration();
+}
+
+void OSIPhone::start() {
+	Main::start();
+
+	if (joypad_iphone) {
+		joypad_iphone->start_processing();
+	}
+}
+
+void OSIPhone::finalize() {
+	deinitialize_modules();
+
+	// Already gets called
+	//    delete_main_loop();
+}
+
+// MARK: Dynamic Libraries
+
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+	if (p_path.length() == 0) {
+		p_library_handle = RTLD_SELF;
+		return OK;
+	}
+	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+}
+
+Error OSIPhone::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 OSIPhone::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 = OSIPhone::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);
+}
+
+void OSIPhone::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());
+}
+
+String OSIPhone::get_name() const {
+	return "iOS";
+};
+
+String OSIPhone::get_model_name() const {
+	String model = ios->get_model();
+	if (model != "")
+		return model;
+
+	return OS_Unix::get_model_name();
+}
+
+Error OSIPhone::shell_open(String p_uri) {
+	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+	NSURL *url = [NSURL URLWithString:urlPath];
+	[urlPath release];
+
+	if (![[UIApplication sharedApplication] canOpenURL:url]) {
+		return ERR_CANT_OPEN;
+	}
+
+	printf("opening url %ls\n", p_uri.c_str());
+
+	//    if (@available(iOS 10, *)) {
+	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+	//    } else {
+	//        [[UIApplication sharedApplication] openURL:url];
+	//    }
+
+	return OK;
+};
+
+void OSIPhone::set_user_data_dir(String p_dir) {
+	DirAccess *da = DirAccess::open(p_dir);
+
+	user_data_dir = da->get_current_dir();
+	printf("setting data dir to %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str());
+	memdelete(da);
+}
+
+String OSIPhone::get_user_data_dir() const {
+	return user_data_dir;
+}
+
+void OSIPhone::set_locale(String p_locale) {
+	locale_code = p_locale;
+}
+
+String OSIPhone::get_locale() const {
+	return locale_code;
+}
+
+void OSIPhone::set_unique_id(String p_id) {
+	unique_id = p_id;
+}
+
+String OSIPhone::get_unique_id() const {
+	return unique_id;
+}
+
+void OSIPhone::vibrate_handheld(int p_duration_ms) {
+	// iOS does not support duration for vibration
+	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
+bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
+	return p_feature == "mobile";
+}
+
+void OSIPhone::on_focus_out() {
+	if (is_focused) {
+		is_focused = false;
+
+		if (DisplayServerIPhone::get_singleton()) {
+			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+		}
+
+		[AppDelegate.viewController.godotView stopRendering];
+
+		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+			DisplayServerIPhone::get_singleton()->native_video_pause();
+		}
+
+		audio_driver.stop();
+	}
+}
+
+void OSIPhone::on_focus_in() {
+	if (!is_focused) {
+		is_focused = true;
+
+		if (DisplayServerIPhone::get_singleton()) {
+			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+		}
+
+		[AppDelegate.viewController.godotView startRendering];
+
+		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+			DisplayServerIPhone::get_singleton()->native_video_unpause();
+		}
+
+		audio_driver.start();
+	}
+}
+
+#endif // IPHONE_ENABLED

+ 9 - 11
platform/iphone/view_controller.h

@@ -31,20 +31,18 @@
 #import <GameKit/GameKit.h>
 #import <UIKit/UIKit.h>
 
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@class GodotView;
 
-- (BOOL)shouldAutorotateToInterfaceOrientation:
-		(UIInterfaceOrientation)p_orientation;
+@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
 
-- (void)didReceiveMemoryWarning;
+- (GodotView *)godotView;
 
-- (void)viewDidLoad;
+// MARK: Native Video Player
 
-- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
-
-- (BOOL)prefersStatusBarHidden;
-
-- (BOOL)prefersHomeIndicatorAutoHidden;
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
 
 @end

+ 309 - 47
platform/iphone/view_controller.mm

@@ -29,96 +29,174 @@
 /*************************************************************************/
 
 #import "view_controller.h"
-
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
 #include "os_iphone.h"
 
-#include "core/project_settings.h"
+#import <GameController/GameController.h>
 
-extern "C" {
+@interface ViewController ()
 
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@property(strong, nonatomic) GodotViewRenderer *renderer;
 
-int add_path(int p_argc, char **p_args) {
-	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
-	if (!str)
-		return p_argc;
+// TODO: separate view to handle video
+// AVPlayer-related properties
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+@property(assign, nonatomic) BOOL videoHasFoundError;
 
-	p_args[p_argc++] = "--path";
-	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
-	p_args[p_argc++] = (char *)[str cString];
-	p_args[p_argc] = NULL;
+@end
 
-	return p_argc;
-};
+@implementation ViewController
 
-int add_cmdline(int p_argc, char **p_args) {
-	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
-	if (!arr)
-		return p_argc;
+- (GodotView *)godotView {
+	return (GodotView *)self.view;
+}
 
-	for (int i = 0; i < [arr count]; i++) {
-		NSString *str = [arr objectAtIndex:i];
-		if (!str)
-			continue;
-		[str retain]; // @todo delete these at some point
-		p_args[p_argc++] = (char *)[str cString];
-	};
+- (void)loadView {
+	GodotView *view = [[GodotView alloc] init];
+	GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
 
-	p_args[p_argc] = NULL;
+	self.renderer = renderer;
+	self.view = view;
 
-	return p_argc;
-};
-}; // extern "C"
+	view.renderer = self.renderer;
 
-@interface ViewController ()
+	[renderer release];
+	[view release];
+}
 
-@end
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+	self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
 
-@implementation ViewController
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+	self = [super initWithCoder:coder];
+
+	if (self) {
+		[self godot_commonInit];
+	}
+
+	return self;
+}
+
+- (void)godot_commonInit {
+	self.isVideoCurrentlyPlaying = NO;
+	self.videoCurrentTime = kCMTimeZero;
+	self.videoHasFoundError = false;
+}
 
 - (void)didReceiveMemoryWarning {
+	[super didReceiveMemoryWarning];
 	printf("*********** did receive memory warning!\n");
-};
+}
 
 - (void)viewDidLoad {
 	[super viewDidLoad];
 
+	[self observeKeyboard];
+	[self observeAudio];
+
 	if (@available(iOS 11.0, *)) {
 		[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
 	}
 }
 
+- (void)observeKeyboard {
+	printf("******** adding observer for keyboard show/hide\n");
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(keyboardOnScreen:)
+				   name:UIKeyboardDidShowNotification
+				 object:nil];
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(keyboardHidden:)
+				   name:UIKeyboardDidHideNotification
+				 object:nil];
+}
+
+- (void)observeAudio {
+	printf("******** adding observer for sound routing changes\n");
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(audioRouteChangeListenerCallback:)
+				   name:AVAudioSessionRouteChangeNotification
+				 object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+	if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+		[self handleVideoOrPlayerStatus];
+	}
+
+	if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+		[self handleVideoPlayRate];
+	}
+}
+
+- (void)dealloc {
+	[self stopVideo];
+
+	self.renderer = nil;
+
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+
+	[super dealloc];
+}
+
+// MARK: Orientation
+
 - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
 	return UIRectEdgeAll;
 }
 
 - (BOOL)shouldAutorotate {
-	switch (OS::get_singleton()->get_screen_orientation()) {
-		case OS::SCREEN_SENSOR:
-		case OS::SCREEN_SENSOR_LANDSCAPE:
-		case OS::SCREEN_SENSOR_PORTRAIT:
+	if (!DisplayServerIPhone::get_singleton()) {
+		return NO;
+	}
+
+	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+		case DisplayServer::SCREEN_SENSOR:
+		case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+		case DisplayServer::SCREEN_SENSOR_PORTRAIT:
 			return YES;
 		default:
 			return NO;
 	}
-};
+}
 
 - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
-	switch (OS::get_singleton()->get_screen_orientation()) {
-		case OS::SCREEN_PORTRAIT:
+	if (!DisplayServerIPhone::get_singleton()) {
+		return UIInterfaceOrientationMaskAll;
+	}
+
+	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+		case DisplayServer::SCREEN_PORTRAIT:
 			return UIInterfaceOrientationMaskPortrait;
-		case OS::SCREEN_REVERSE_LANDSCAPE:
+		case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
 			return UIInterfaceOrientationMaskLandscapeRight;
-		case OS::SCREEN_REVERSE_PORTRAIT:
+		case DisplayServer::SCREEN_REVERSE_PORTRAIT:
 			return UIInterfaceOrientationMaskPortraitUpsideDown;
-		case OS::SCREEN_SENSOR_LANDSCAPE:
+		case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
 			return UIInterfaceOrientationMaskLandscape;
-		case OS::SCREEN_SENSOR_PORTRAIT:
+		case DisplayServer::SCREEN_SENSOR_PORTRAIT:
 			return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
-		case OS::SCREEN_SENSOR:
+		case DisplayServer::SCREEN_SENSOR:
 			return UIInterfaceOrientationMaskAll;
-		case OS::SCREEN_LANDSCAPE:
+		case DisplayServer::SCREEN_LANDSCAPE:
 			return UIInterfaceOrientationMaskLandscapeLeft;
 	}
 };
@@ -135,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {
 	}
 }
 
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+	NSDictionary *info = notification.userInfo;
+	NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+	CGRect rawFrame = [value CGRectValue];
+	CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+	if (DisplayServerIPhone::get_singleton()) {
+		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+	}
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+	if (DisplayServerIPhone::get_singleton()) {
+		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
+	}
+}
+
+// MARK: Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+	printf("*********** route changed!\n");
+	NSDictionary *interuptionDict = notification.userInfo;
+
+	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+	switch (routeChangeReason) {
+		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+			NSLog(@"Headphone/Line plugged in");
+		} break;
+		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+			NSLog(@"Headphone/Line was pulled. Resuming video play....");
+			if ([self isVideoPlaying]) {
+				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+					[self.avPlayer play]; // NOTE: change this line according your current player implementation
+					NSLog(@"resumed play");
+				});
+			}
+		} break;
+		case AVAudioSessionRouteChangeReasonCategoryChange: {
+			// called at start - also when other audio wants to play
+			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+		} break;
+	}
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+	if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+		[self stopVideo];
+		self.videoHasFoundError = true;
+	}
+
+	if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+		//        NSLog(@"time: %@", self.video_current_time);
+		[self.avPlayer seekToTime:self.videoCurrentTime];
+		self.videoCurrentTime = kCMTimeZero;
+	}
+}
+
+- (void)handleVideoPlayRate {
+	NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+	if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+			[self.avPlayer play]; // NOTE: change this line according your current player implementation
+			NSLog(@"resumed play");
+		});
+
+		NSLog(@" . . . PAUSED (or just started)");
+	}
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+	self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+	self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+	[self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+	self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+	self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+	[self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+	[[NSNotificationCenter defaultCenter]
+			addObserver:self
+			   selector:@selector(playerItemDidReachEnd:)
+				   name:AVPlayerItemDidPlayToEndTimeNotification
+				 object:[self.avPlayer currentItem]];
+
+	[self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+	[self.avPlayerLayer setFrame:self.view.bounds];
+	[self.view.layer addSublayer:self.avPlayerLayer];
+	[self.avPlayer play];
+
+	AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+	NSMutableArray *allAudioParams = [NSMutableArray array];
+	for (id track in audioGroup.options) {
+		NSString *language = [[track locale] localeIdentifier];
+		NSLog(@"subtitle lang: %@", language);
+
+		if ([language isEqualToString:audioTrack]) {
+			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+			[audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+			[audioInputParams setTrackID:[track trackID]];
+			[allAudioParams addObject:audioInputParams];
+
+			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+			[audioMix setInputParameters:allAudioParams];
+
+			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+			[self.avPlayer.currentItem setAudioMix:audioMix];
+
+			break;
+		}
+	}
+
+	AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+	for (id track in useableTracks) {
+		NSString *language = [[track locale] localeIdentifier];
+		NSLog(@"subtitle lang: %@", language);
+
+		if ([language isEqualToString:subtitleTrack]) {
+			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+			break;
+		}
+	}
+
+	self.isVideoCurrentlyPlaying = YES;
+
+	return true;
+}
+
+- (BOOL)isVideoPlaying {
+	if (self.avPlayer.error) {
+		printf("Error during playback\n");
+	}
+	return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+	self.videoCurrentTime = self.avPlayer.currentTime;
+	[self.avPlayer pause];
+	self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unpauseVideo {
+	[self.avPlayer play];
+	self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+	[self stopVideo];
+}
+
+- (void)stopVideo {
+	[self.avPlayer pause];
+	[self.avPlayerLayer removeFromSuperlayer];
+	self.avPlayerLayer = nil;
+
+	if (self.avPlayerItem) {
+		[self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+		self.avPlayerItem = nil;
+	}
+
+	if (self.avPlayer) {
+		[self.avPlayer removeObserver:self forKeyPath:@"status"];
+		self.avPlayer = nil;
+	}
+
+	self.avAsset = nil;
+
+	self.isVideoCurrentlyPlaying = NO;
+}
+
+// MARK: Delegates
+
 #ifdef GAME_CENTER_ENABLED
 - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
 	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone

+ 3 - 2
platform/iphone/vulkan_context_iphone.h

@@ -32,13 +32,14 @@
 #define VULKAN_CONTEXT_IPHONE_H
 
 #include "drivers/vulkan/vulkan_context.h"
-// #import <UIKit/UIKit.h>
+
+#import <UIKit/UIKit.h>
 
 class VulkanContextIPhone : public VulkanContext {
 	virtual const char *_get_platform_surface_extension() const;
 
 public:
-	int window_create(void *p_window, int p_width, int p_height);
+	Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
 
 	VulkanContextIPhone();
 	~VulkanContextIPhone();

+ 12 - 10
platform/iphone/vulkan_context_iphone.mm

@@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
 	return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
 }
 
-int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) {
+Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
+		CALayer *p_metal_layer, int p_width,
+		int p_height) {
 	VkIOSSurfaceCreateInfoMVK createInfo;
-	createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+	createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
 	createInfo.pNext = NULL;
 	createInfo.flags = 0;
-	createInfo.pView = p_window;
+	createInfo.pView = p_metal_layer;
 
 	VkSurfaceKHR surface;
-	VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
-	ERR_FAIL_COND_V(err, -1);
-	return _window_create(surface, p_width, p_height);
-}
+	VkResult err =
+			vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+	ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
 
-VulkanContextIPhone::VulkanContextIPhone() {
+	return _window_create(p_window_id, surface, p_width, p_height);
 }
 
-VulkanContextIPhone::~VulkanContextIPhone() {
-}
+VulkanContextIPhone::VulkanContextIPhone() {}
+
+VulkanContextIPhone::~VulkanContextIPhone() {}

+ 1 - 1
scene/gui/scroll_bar.cpp

@@ -522,7 +522,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
 			drag_node_accum = Vector2();
 			last_drag_node_accum = Vector2();
 			drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
-			drag_node_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+			drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
 			drag_node_touching_deaccel = false;
 			time_since_motion = 0;
 

+ 29 - 0
thirdparty/vulkan/patches/VMA-assert-remove.patch

@@ -0,0 +1,29 @@
+diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
+index 0dfb66efc6..8a42699e7f 100644
+--- a/thirdparty/vulkan/vk_mem_alloc.h
++++ b/thirdparty/vulkan/vk_mem_alloc.h
+@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
+         allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
+             requiresDedicatedAllocation, prefersDedicatedAllocation);
+ 
+-        // Make sure alignment requirements for specific buffer usages reported
+-        // in Physical Device Properties are included in alignment reported by memory requirements.
+-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
+-        {
+-           VMA_ASSERT(vkMemReq.alignment %
+-              allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
+-        }
+-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
+-        {
+-           VMA_ASSERT(vkMemReq.alignment %
+-              allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
+-        }
+-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
+-        {
+-           VMA_ASSERT(vkMemReq.alignment %
+-              allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
+-        }
+-
+         // 3. Allocate memory using allocator.
+         res = allocator->AllocateMemory(
+             vkMemReq,

+ 0 - 18
thirdparty/vulkan/vk_mem_alloc.h

@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
         allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
             requiresDedicatedAllocation, prefersDedicatedAllocation);
 
-        // Make sure alignment requirements for specific buffer usages reported
-        // in Physical Device Properties are included in alignment reported by memory requirements.
-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
-        {
-           VMA_ASSERT(vkMemReq.alignment %
-              allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
-        }
-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
-        {
-           VMA_ASSERT(vkMemReq.alignment %
-              allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
-        }
-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
-        {
-           VMA_ASSERT(vkMemReq.alignment %
-              allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
-        }
-
         // 3. Allocate memory using allocator.
         res = allocator->AllocateMemory(
             vkMemReq,