Merge pull request #40434 from naithar/feature/ios-moltenVK
[iOS] Basic Vulkan/Metal Support
This commit is contained in:
commit
9856c8fda4
|
@ -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,4 +1,4 @@
|
|||
name: MacOS Builds
|
||||
name: macOS Builds
|
||||
on: [push, pull_request]
|
||||
|
||||
# Global Cache Settings
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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=[
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"file_format_version" : "1.0.0",
|
||||
"ICD": {
|
||||
"library_path": "./../../Frameworks/MoltenVK.framework/MoltenVK",
|
||||
"api_version" : "1.0.0"
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
[ar_session runWithConfiguration:configuration];
|
||||
}
|
||||
|
||||
// make sure our camera is on
|
||||
if (feed.is_valid()) {
|
||||
feed->set_active(true);
|
||||
}
|
||||
|
||||
[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;
|
||||
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];
|
||||
///@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
|
||||
|
||||
for (ARHitTestResult *result in results) {
|
||||
Transform transform;
|
||||
NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
|
||||
|
||||
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];
|
||||
for (ARHitTestResult *result in results) {
|
||||
Transform transform;
|
||||
|
||||
/* important, NOT scaled to world_scale !! */
|
||||
arr.push_back(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;
|
||||
} else {
|
||||
return Array();
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// yeah!
|
||||
initialized = true;
|
||||
|
||||
// Start our session...
|
||||
start_session();
|
||||
}
|
||||
feed->set_active(true);
|
||||
|
||||
// yeah!
|
||||
initialized = true;
|
||||
|
||||
// Start our session...
|
||||
start_session();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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_
|
||||
// _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];
|
||||
|
||||
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...
|
||||
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;
|
||||
// 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.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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
for (int i = 0; i < feeds.size() && !found; i++) {
|
||||
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:deviceTypes
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
|
||||
// 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() {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
+ (ViewController *)viewController {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
- (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];
|
||||
self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
|
||||
|
||||
OS::VideoMode vm = _get_video_mode();
|
||||
|
||||
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;
|
||||
ViewController *viewController = [[ViewController alloc] init];
|
||||
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
|
||||
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
|
||||
|
||||
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
|
||||
|
||||
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)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 {
|
||||
[self deinitGameControllers];
|
||||
|
||||
if (motionInitialised) {
|
||||
///@TODO is this the right place to clean this up?
|
||||
[motionManager stopDeviceMotionUpdates];
|
||||
[motionManager release];
|
||||
motionManager = nil;
|
||||
motionInitialised = NO;
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
|
||||
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];
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(params["product_id"]).utf8().get_data()] autorelease];
|
||||
SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
|
||||
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;
|
||||
}
|
||||
|
||||
SKPayment *payment = [SKPayment paymentWithProduct:product];
|
||||
SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
|
||||
[defq addPayment:payment];
|
||||
printf("purchase sent!\n");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
JoypadIPhone *joypad_iphone;
|
||||
|
||||
MainLoop *main_loop;
|
||||
|
||||
#if defined(VULKAN_ENABLED)
|
||||
VulkanContextIPhone *context_vulkan;
|
||||
RenderingDeviceVulkan *rendering_device_vulkan;
|
||||
#endif
|
||||
VideoMode video_mode;
|
||||
virtual void initialize_core() override;
|
||||
virtual void initialize() override;
|
||||
|
||||
virtual int get_video_driver_count() const;
|
||||
virtual const char *get_video_driver_name(int p_driver) const;
|
||||
virtual void initialize_joypads() override {
|
||||
}
|
||||
|
||||
virtual int get_current_video_driver() const;
|
||||
virtual void set_main_loop(MainLoop *p_main_loop) override;
|
||||
virtual MainLoop *get_main_loop() const override;
|
||||
|
||||
virtual void initialize_core();
|
||||
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
|
||||
virtual void delete_main_loop() override;
|
||||
|
||||
virtual void set_main_loop(MainLoop *p_main_loop);
|
||||
virtual MainLoop *get_main_loop() const;
|
||||
virtual void finalize() override;
|
||||
|
||||
virtual void delete_main_loop();
|
||||
|
||||
virtual void finalize();
|
||||
|
||||
struct MouseList {
|
||||
bool pressed[MAX_MOUSE_COUNT];
|
||||
MouseList() {
|
||||
for (int i = 0; i < MAX_MOUSE_COUNT; i++)
|
||||
pressed[i] = false;
|
||||
};
|
||||
};
|
||||
|
||||
MouseList touch_list;
|
||||
|
||||
Vector3 last_accel;
|
||||
|
||||
Ref<InputEvent> event_queue[MAX_EVENTS];
|
||||
int event_count;
|
||||
void queue_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
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);
|
||||
OSIPhone(String p_data_dir);
|
||||
~OSIPhone();
|
||||
|
||||
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
|
||||
void initialize_modules();
|
||||
|
||||
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);
|
||||
bool iterate();
|
||||
|
||||
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;
|
||||
void start();
|
||||
|
||||
virtual void set_keep_screen_on(bool p_enabled);
|
||||
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 can_draw() const;
|
||||
virtual void alert(const String &p_alert,
|
||||
const String &p_title = "ALERT!") 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 String get_name() const override;
|
||||
virtual String get_model_name() const override;
|
||||
|
||||
virtual Size2 get_window_size() const;
|
||||
virtual Rect2 get_window_safe_area() const;
|
||||
virtual Error shell_open(String p_uri) override;
|
||||
|
||||
virtual bool has_touchscreen_ui_hint() const;
|
||||
|
||||
void set_data_dir(String p_dir);
|
||||
|
||||
virtual String get_name() const;
|
||||
virtual String get_model_name() const;
|
||||
|
||||
Error shell_open(String p_uri);
|
||||
|
||||
String get_user_data_dir() const;
|
||||
void set_user_data_dir(String p_dir);
|
||||
virtual String get_user_data_dir() const override;
|
||||
|
||||
void set_locale(String p_locale);
|
||||
String get_locale() const;
|
||||
virtual String get_locale() const override;
|
||||
|
||||
void set_unique_id(String p_id);
|
||||
String get_unique_id() const;
|
||||
virtual String get_unique_id() const override;
|
||||
|
||||
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 void vibrate_handheld(int p_duration_ms = 500) override;
|
||||
|
||||
virtual bool _check_internal_feature_support(const String &p_feature);
|
||||
OSIPhone(int width, int height, String p_data_dir);
|
||||
~OSIPhone();
|
||||
virtual bool _check_internal_feature_support(const String &p_feature) override;
|
||||
|
||||
void on_focus_out();
|
||||
void on_focus_in();
|
||||
};
|
||||
|
||||
#endif // OS_IPHONE_H
|
||||
|
||||
#endif
|
||||
#endif // IPHONE_ENABLED
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
extern "C" {
|
||||
|
||||
int add_path(int, char **);
|
||||
int add_cmdline(int, char **);
|
||||
|
||||
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++] = "--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;
|
||||
|
||||
return p_argc;
|
||||
};
|
||||
|
||||
int add_cmdline(int p_argc, char **p_args) {
|
||||
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
|
||||
if (!arr)
|
||||
return p_argc;
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
p_args[p_argc] = NULL;
|
||||
|
||||
return p_argc;
|
||||
};
|
||||
}; // extern "C"
|
||||
#import <GameController/GameController.h>
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
@property(strong, nonatomic) GodotViewRenderer *renderer;
|
||||
|
||||
// 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;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (GodotView *)godotView {
|
||||
return (GodotView *)self.view;
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
GodotView *view = [[GodotView alloc] init];
|
||||
GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
|
||||
|
||||
self.renderer = renderer;
|
||||
self.view = view;
|
||||
|
||||
view.renderer = self.renderer;
|
||||
|
||||
[renderer release];
|
||||
[view release];
|
||||
}
|
||||
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
|
||||
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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
return _window_create(p_window_id, surface, p_width, p_height);
|
||||
}
|
||||
|
||||
VulkanContextIPhone::VulkanContextIPhone() {
|
||||
}
|
||||
VulkanContextIPhone::VulkanContextIPhone() {}
|
||||
|
||||
VulkanContextIPhone::~VulkanContextIPhone() {
|
||||
}
|
||||
VulkanContextIPhone::~VulkanContextIPhone() {}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue