diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 45c96beb2808..f54bb4cb2811 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -40,7 +40,8 @@
/drivers/vulkan/ @godotengine/rendering
## OS
-/drivers/apple*/ @godotengine/macos
+/drivers/apple/ @godotengine/macos
+/drivers/apple_embedded/ @godotengine/ios
/drivers/unix/ @godotengine/linux-bsd
/drivers/windows/ @godotengine/windows
@@ -177,6 +178,7 @@
/modules/openxr/ @godotengine/xr
/modules/openxr/doc_classes/ @godotengine/xr @godotengine/documentation
/modules/openxr/editor/ @godotengine/xr @godotengine/editor
+/modules/visionos_xr/ @godotengine/xr
/modules/webxr/ @godotengine/xr
/modules/webxr/doc_classes/ @godotengine/xr @godotengine/documentation
@@ -217,6 +219,8 @@
/platform/linuxbsd/doc_classes/ @godotengine/linux-bsd @godotengine/documentation
/platform/macos/ @godotengine/macos
/platform/macos/doc_classes/ @godotengine/macos @godotengine/documentation
+/platform/tvos/ @godotengine/ios
+/platform/tvos/doc_classes/ @godotengine/ios @godotengine/documentation
/platform/visionos/ @godotengine/xr
/platform/visionos/doc_classes/ @godotengine/xr @godotengine/documentation
/platform/web/ @godotengine/web
diff --git a/.github/changed_files.yml b/.github/changed_files.yml
index 892ac77fe431..42628459aae1 100644
--- a/.github/changed_files.yml
+++ b/.github/changed_files.yml
@@ -25,5 +25,5 @@ clangd:
- "!editor/shader/shader_baker/shader_baker_export_plugin_platform_{d3d12,metal}.{h,cpp}"
- "!modules/camera/camera_{android,macos,win}.{h,cpp}"
- "!modules/openxr/extensions/platform/openxr_{android,metal}_extension.{h,cpp}"
- - "!platform/{android,ios,macos,visionos,web,windows}/**"
- - "platform/{android,ios,macos,visionos,web,windows}/{api,export}/*.{h,hpp,hxx,hh,c,cpp,cxx,cc}"
+ - "!platform/{android,ios,macos,tvos,visionos,web,windows}/**"
+ - "platform/{android,ios,macos,tvos,visionos,web,windows}/{api,export}/*.{h,hpp,hxx,hh,c,cpp,cxx,cc}"
diff --git a/core/os/os.cpp b/core/os/os.cpp
index ffc0dff1c747..6742ca2406f2 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -609,7 +609,7 @@ bool OS::has_feature(const String &p_feature) {
}
#endif
-#if defined(IOS_SIMULATOR) || defined(VISIONOS_SIMULATOR)
+#if defined(IOS_SIMULATOR) || defined(TVOS_SIMULATOR) || defined(VISIONOS_SIMULATOR)
if (p_feature == "simulator") {
return true;
}
diff --git a/doc/classes/EditorExportPlatformAppleEmbedded.xml b/doc/classes/EditorExportPlatformAppleEmbedded.xml
index 507302d23567..4e7a1e64f5aa 100644
--- a/doc/classes/EditorExportPlatformAppleEmbedded.xml
+++ b/doc/classes/EditorExportPlatformAppleEmbedded.xml
@@ -1,10 +1,10 @@
- Base class for the Apple embedded platform exporters (iOS and visionOS).
+ Base class for the Apple embedded platform exporters (iOS, tvOS, and visionOS).
- The base class for Apple embedded platform exporters. These include iOS and visionOS, but not macOS. See the classes inheriting from this one for more details.
+ The base class for Apple embedded platform exporters. These include iOS, tvOS, and visionOS, but not macOS. See the classes inheriting from this one for more details.
$DOCS_URL/tutorials/export/exporting_for_ios.html
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 8dc106956c32..a0e4f571eac4 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -2847,6 +2847,9 @@
Support for high dynamic range (HDR) output.
+
+ Support for rasterization rate maps. The current implementation targets Metal on Apple platforms. This allows for foveated rendering, which is used by the visionOS XR module.
+
Maximum number of uniform sets that can be bound at a given time.
diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml
index 3b19e2fe103d..47298cf50255 100644
--- a/doc/classes/XRInterface.xml
+++ b/doc/classes/XRInterface.xml
@@ -284,5 +284,8 @@
The texture format is the same as expected by the Vulkan [code]VK_EXT_fragment_density_map[/code] extension.
+
+ The texture contains a Metal [code]rasterizationRateMap[/code], used for foveated rendering on Apple platforms. It's not a real texture, but instead an opaque wrapper for an [code]MTLRasterizationRateMap[/code] object.
+
diff --git a/drivers/SCsub b/drivers/SCsub
index a1eb8510500c..955c55054ca2 100644
--- a/drivers/SCsub
+++ b/drivers/SCsub
@@ -26,10 +26,10 @@ if env["xaudio2"]:
SConscript("xaudio2/SCsub")
# Shared Apple platform drivers
-if env["platform"] in ["macos", "ios", "visionos"]:
+if env["platform"] in ["macos", "ios", "tvos", "visionos"]:
SConscript("apple/SCsub")
SConscript("coreaudio/SCsub")
-if env["platform"] in ["ios", "visionos"]:
+if env["platform"] in ["ios", "tvos", "visionos"]:
SConscript("apple_embedded/SCsub")
# Accessibility
@@ -61,7 +61,7 @@ if env["metal"]:
SConscript("metal/SCsub")
# Input drivers
-if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows", "ios", "visionos"]:
+if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows", "ios", "tvos", "visionos"]:
# TODO: Evaluate support for Android and Web.
SConscript("sdl/SCsub")
diff --git a/drivers/apple_embedded/apple_embedded.mm b/drivers/apple_embedded/apple_embedded.mm
index b6b5410e6fd3..c47c431d56ee 100644
--- a/drivers/apple_embedded/apple_embedded.mm
+++ b/drivers/apple_embedded/apple_embedded.mm
@@ -31,7 +31,7 @@
#import "apple_embedded.h"
#include "core/object/class_db.h"
-#import "drivers/apple_embedded/app_delegate_service.h"
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_controller.h"
#import
diff --git a/drivers/apple_embedded/bridging_header_apple_embedded.h b/drivers/apple_embedded/bridging_header_apple_embedded.h
index 5e0bb8b26210..adb730ae6faa 100644
--- a/drivers/apple_embedded/bridging_header_apple_embedded.h
+++ b/drivers/apple_embedded/bridging_header_apple_embedded.h
@@ -31,8 +31,7 @@
#pragma once
// IWYU pragma: begin_exports.
-#import "drivers/apple_embedded/app_delegate_service.h"
-#import "drivers/apple_embedded/godot_app_delegate.h"
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_controller.h"
// IWYU pragma: end_exports.
diff --git a/drivers/apple_embedded/display_server_apple_embedded.mm b/drivers/apple_embedded/display_server_apple_embedded.mm
index 3e0915151e8e..26c5db3fe252 100644
--- a/drivers/apple_embedded/display_server_apple_embedded.mm
+++ b/drivers/apple_embedded/display_server_apple_embedded.mm
@@ -34,8 +34,8 @@
#include "core/input/input.h"
#include "core/io/file_access_pack.h"
#include "core/os/os.h"
-#import "drivers/apple_embedded/app_delegate_service.h"
#import "drivers/apple_embedded/apple_embedded.h"
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
#import "drivers/apple_embedded/godot_keyboard_input_view.h"
#import "drivers/apple_embedded/godot_view_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_controller.h"
@@ -66,6 +66,15 @@
bool has_made_render_compositor_current = false;
+#if !defined(GLES3_ENABLED) && defined(METAL_ENABLED)
+ if (rendering_driver == "opengl3") {
+ WARN_PRINT("OpenGL 3 is not supported on this platform, switching to Metal.");
+ rendering_driver = "metal";
+ OS::get_singleton()->set_current_rendering_method("mobile", OS::RENDERING_SOURCE_FALLBACK);
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver, OS::RENDERING_SOURCE_FALLBACK);
+ }
+#endif
+
#if defined(RD_ENABLED)
rendering_context = nullptr;
rendering_device = nullptr;
@@ -668,7 +677,7 @@
if (@available(iOS 16.0, *)) {
[GDTAppDelegateService.viewController setNeedsUpdateOfSupportedInterfaceOrientations];
}
-#if !defined(VISIONOS_ENABLED)
+#if !defined(VISIONOS_ENABLED) && !defined(TVOS_ENABLED)
else {
[UIViewController attemptRotationToDeviceOrientation];
}
@@ -772,13 +781,19 @@ _FORCE_INLINE_ int _convert_utf32_offset_to_utf16(const String &p_existing_text,
}
void DisplayServerAppleEmbedded::clipboard_set(const String &p_text) {
+#ifndef TVOS_ENABLED
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8().get_data()];
+#endif
}
String DisplayServerAppleEmbedded::clipboard_get() const {
+#ifndef TVOS_ENABLED
NSString *text = [UIPasteboard generalPasteboard].string;
return String::utf8([text UTF8String]);
+#else
+ return String();
+#endif
}
void DisplayServerAppleEmbedded::screen_set_keep_on(bool p_enable) {
diff --git a/drivers/apple_embedded/godot_app_delegate.h b/drivers/apple_embedded/godot_app_delegate_apple_embedded.h
similarity index 94%
rename from drivers/apple_embedded/godot_app_delegate.h
rename to drivers/apple_embedded/godot_app_delegate_apple_embedded.h
index 09c0b7866066..ce2985a6905d 100644
--- a/drivers/apple_embedded/godot_app_delegate.h
+++ b/drivers/apple_embedded/godot_app_delegate_apple_embedded.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* godot_app_delegate.h */
+/* godot_app_delegate_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -34,7 +34,7 @@
typedef NSObject GDTAppDelegateServiceProtocol;
-@interface GDTApplicationDelegate : NSObject
+@interface GDTAppDelegate : NSObject
@property(class, readonly, strong) NSArray *services;
diff --git a/drivers/apple_embedded/godot_app_delegate.mm b/drivers/apple_embedded/godot_app_delegate_apple_embedded.mm
similarity index 97%
rename from drivers/apple_embedded/godot_app_delegate.mm
rename to drivers/apple_embedded/godot_app_delegate_apple_embedded.mm
index b3360e4a018b..4392a20c6fbc 100644
--- a/drivers/apple_embedded/godot_app_delegate.mm
+++ b/drivers/apple_embedded/godot_app_delegate_apple_embedded.mm
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* godot_app_delegate.mm */
+/* godot_app_delegate_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,12 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#import "godot_app_delegate.h"
+#import "godot_app_delegate_apple_embedded.h"
#include "core/typedefs.h"
-#import "drivers/apple_embedded/app_delegate_service.h"
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
-@implementation GDTApplicationDelegate
+@implementation GDTAppDelegate
static NSMutableArray *services = nil;
@@ -43,7 +43,7 @@ @implementation GDTApplicationDelegate
+ (void)load {
services = [NSMutableArray new];
- [services addObject:[GDTAppDelegateService new]];
+ // Add the specific GDTAppDelegateService subclass in each inheriting platform
}
+ (void)addService:(GDTAppDelegateServiceProtocol *)service {
@@ -113,7 +113,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0), tvos(13.0), visionos(1.0)) {
UISceneConfiguration *config = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
- config.delegateClass = [GDTApplicationDelegate class];
+ config.delegateClass = [GDTAppDelegate class];
return config;
}
@@ -409,6 +409,8 @@ - (void)application:(UIApplication *)application didFailToContinueUserActivityWi
}
}
+#ifndef TVOS_ENABLED
+
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
for (GDTAppDelegateServiceProtocol *service in services) {
if (![service respondsToSelector:_cmd]) {
@@ -419,6 +421,8 @@ - (void)application:(UIApplication *)application performActionForShortcutItem:(U
}
}
+#endif
+
// MARK: WatchKit
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
diff --git a/drivers/apple_embedded/app_delegate_service.h b/drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h
similarity index 94%
rename from drivers/apple_embedded/app_delegate_service.h
rename to drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h
index bbf6bd79fc8d..0fd4129d9c5e 100644
--- a/drivers/apple_embedded/app_delegate_service.h
+++ b/drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* app_delegate_service.h */
+/* godot_app_delegate_service_apple_embedded.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -36,6 +36,6 @@
@interface GDTAppDelegateService : NSObject
-@property(strong, class, nonatomic) GDTViewController *viewController;
+@property(weak, class, nonatomic, nullable) GDTViewController *viewController;
@end
diff --git a/drivers/apple_embedded/app_delegate_service.mm b/drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm
similarity index 96%
rename from drivers/apple_embedded/app_delegate_service.mm
rename to drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm
index 4e3ce1b443f4..0eb9c9a36734 100644
--- a/drivers/apple_embedded/app_delegate_service.mm
+++ b/drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* app_delegate_service.mm */
+/* godot_app_delegate_service_apple_embedded.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#import "app_delegate_service.h"
+#import "godot_app_delegate_service_apple_embedded.h"
#include "core/config/project_settings.h"
#include "core/os/main_loop.h"
@@ -59,14 +59,14 @@ @implementation GDTAppDelegateService
SESSION_CATEGORY_SOLO_AMBIENT
};
-static GDTViewController *mainViewController = nil;
+static __weak GDTViewController *_viewController = nil;
+ (GDTViewController *)viewController {
- return mainViewController;
+ return _viewController;
}
+ (void)setViewController:(GDTViewController *)viewController {
- mainViewController = viewController;
+ _viewController = viewController;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
@@ -111,7 +111,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
category = AVAudioSessionCategoryMultiRoute;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) {
category = AVAudioSessionCategoryPlayAndRecord;
+#ifndef TVOS_ENABLED
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
+#endif
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
options |= AVAudioSessionCategoryOptionAllowAirPlay;
} else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) {
diff --git a/drivers/apple_embedded/godot_renderer.h b/drivers/apple_embedded/godot_renderer.h
new file mode 100644
index 000000000000..eb553953cfd5
--- /dev/null
+++ b/drivers/apple_embedded/godot_renderer.h
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* godot_renderer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import
+
+inline void safeDispatchSyncToMain(void (^block)(void)) {
+ if ([NSThread isMainThread]) {
+ block();
+ } else {
+ dispatch_sync(dispatch_get_main_queue(), block);
+ }
+}
+
+@interface GDTRenderer : NSObject
+
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+
+- (BOOL)setUp;
+
+@end
diff --git a/drivers/apple_embedded/godot_renderer.mm b/drivers/apple_embedded/godot_renderer.mm
new file mode 100644
index 000000000000..8065e2aabc21
--- /dev/null
+++ b/drivers/apple_embedded/godot_renderer.mm
@@ -0,0 +1,107 @@
+/**************************************************************************/
+/* godot_renderer.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_renderer.h"
+
+#include "core/config/project_settings.h"
+#import "drivers/apple_embedded/display_server_apple_embedded.h"
+#import "drivers/apple_embedded/os_apple_embedded.h"
+#include "main/main.h"
+
+@interface GDTRenderer ()
+
+@property(assign, nonatomic) BOOL hasCalledProjectDataSetUp;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetUp;
+
+@end
+
+@implementation GDTRenderer
+
+- (BOOL)setUp {
+ if (self.hasFinishedSetUp) {
+ return NO;
+ }
+
+ if (!OS::get_singleton()) {
+ exit(0);
+ }
+
+ if (!self.hasCalledProjectDataSetUp) {
+ [self setUpProjectData];
+ }
+
+ if (!self.hasStartedMain) {
+ [self startMain];
+ }
+
+ self.hasFinishedSetUp = YES;
+
+ return NO;
+}
+
+- (void)setUpProjectData {
+ self.hasCalledProjectDataSetUp = YES;
+ safeDispatchSyncToMain(^{
+ 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)startMain {
+ self.hasStartedMain = YES;
+ safeDispatchSyncToMain(^{
+ OS_AppleEmbedded::get_singleton()->start();
+ });
+}
+
+@end
diff --git a/drivers/apple_embedded/app.swift b/drivers/apple_embedded/godot_swiftui_view_controller.swift
similarity index 93%
rename from drivers/apple_embedded/app.swift
rename to drivers/apple_embedded/godot_swiftui_view_controller.swift
index 4b03603948d6..a1fb52a45adb 100644
--- a/drivers/apple_embedded/app.swift
+++ b/drivers/apple_embedded/godot_swiftui_view_controller.swift
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* app.swift */
+/* godot_swiftui_view_controller.swift */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -45,10 +45,7 @@ struct GodotSwiftUIViewController: UIViewControllerRepresentable {
}
-@main
-struct SwiftUIApp: App {
- @UIApplicationDelegateAdaptor(GDTApplicationDelegate.self) var appDelegate
-
+struct GodotWindowScene: Scene {
var body: some Scene {
WindowGroup {
GodotSwiftUIViewController()
diff --git a/drivers/apple_embedded/godot_view_apple_embedded.h b/drivers/apple_embedded/godot_view_apple_embedded.h
index 7d5d16141a5d..7c223143ab5c 100644
--- a/drivers/apple_embedded/godot_view_apple_embedded.h
+++ b/drivers/apple_embedded/godot_view_apple_embedded.h
@@ -35,8 +35,8 @@
class String;
@class GDTView;
+@class GDTViewRenderer;
@protocol GDTDisplayLayer;
-@protocol GDTViewRendererProtocol;
@protocol GDTViewDelegate
@@ -46,8 +46,8 @@ class String;
@interface GDTView : UIView
-@property(assign, nonatomic) id renderer;
-@property(assign, nonatomic) id delegate;
+@property(weak, nonatomic) GDTViewRenderer *renderer;
+@property(weak, nonatomic) id delegate;
@property(assign, readonly, nonatomic) BOOL isActive;
diff --git a/drivers/apple_embedded/godot_view_apple_embedded.mm b/drivers/apple_embedded/godot_view_apple_embedded.mm
index ffd22c342816..2b2a3e949414 100644
--- a/drivers/apple_embedded/godot_view_apple_embedded.mm
+++ b/drivers/apple_embedded/godot_view_apple_embedded.mm
@@ -37,13 +37,19 @@
#import "drivers/apple_embedded/display_server_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_renderer.h"
+#ifndef TVOS_ENABLED
#import
+#endif
+#ifndef TVOS_ENABLED
static const int max_touches = 32;
static const float earth_gravity = 9.80665;
+#endif
@interface GDTView () {
+#ifndef TVOS_ENABLED
UITouch *godot_touches[max_touches];
+#endif
CGFloat last_edr_headroom;
}
@@ -58,7 +64,9 @@ @interface GDTView () {
@property(strong, nonatomic) CALayer *renderingLayer;
+#ifndef TVOS_ENABLED
@property(strong, nonatomic) CMMotionManager *motionManager;
+#endif
@property(assign, nonatomic) BOOL delegateDidFinishSetUp;
@@ -102,10 +110,12 @@ - (void)dealloc {
self.renderingLayer = nil;
}
+#ifndef TVOS_ENABLED
if (self.motionManager) {
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
+#endif
if (self.displayLink) {
[self.displayLink invalidate];
@@ -123,7 +133,9 @@ - (void)godot_commonInit {
self.useCADisplayLink = bool(GLOBAL_DEF("display.AppleEmbedded/use_cadisplaylink", true)) ? YES : NO;
last_edr_headroom = 0.0;
-#if !defined(VISIONOS_ENABLED)
+#ifdef TVOS_ENABLED
+ self.contentScaleFactor = self.window.windowScene.screen.scale;
+#elif !defined(VISIONOS_ENABLED)
self.contentScaleFactor = [UIScreen mainScreen].scale;
#endif
@@ -131,6 +143,7 @@ - (void)godot_commonInit {
[self registerForTraitChanges:@[ [UITraitUserInterfaceStyle class] ] withTarget:self action:@selector(traitCollectionDidChangeWithView:previousTraitCollection:)];
}
+#ifndef TVOS_ENABLED
[self initTouches];
self.multipleTouchEnabled = YES;
@@ -145,6 +158,7 @@ - (void)godot_commonInit {
self.motionManager = nil;
}
}
+#endif
}
- (void)system_theme_changed {
@@ -156,7 +170,7 @@ - (void)system_theme_changed {
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
if (@available(iOS 13.0, *)) {
-#if !defined(VISIONOS_ENABLED)
+#if !defined(VISIONOS_ENABLED) && !defined(TVOS_ENABLED)
[super traitCollectionDidChange:previousTraitCollection];
#endif
[self traitCollectionDidChangeWithView:self
@@ -179,7 +193,7 @@ - (void)stopRendering {
self.isActive = NO;
- print_verbose("Stop animation!");
+ print_verbose("Stop rendering");
if (self.useCADisplayLink) {
[self.displayLink invalidate];
@@ -189,7 +203,9 @@ - (void)stopRendering {
self.animationTimer = nil;
}
+#ifndef TVOS_ENABLED
[self clearTouches];
+#endif
}
- (void)startRendering {
@@ -199,7 +215,7 @@ - (void)startRendering {
self.isActive = YES;
- print_verbose("Start animation!");
+ print_verbose("Start rendering");
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
@@ -237,7 +253,7 @@ - (void)drawView {
return;
}
- if ([self.renderer setupView:self]) {
+ if ([self.renderer setUp]) {
return;
}
@@ -249,11 +265,17 @@ - (void)drawView {
}
}
+#ifndef TVOS_ENABLED
[self handleMotion];
+#endif
#if !defined(VISIONOS_ENABLED)
if (@available(iOS 16.0, *)) {
+#ifdef TVOS_ENABLED
+ CGFloat edr_headroom = self.window.windowScene.screen.currentEDRHeadroom;
+#else
CGFloat edr_headroom = UIScreen.mainScreen.currentEDRHeadroom;
+#endif
if (last_edr_headroom != edr_headroom) {
last_edr_headroom = edr_headroom;
if (DisplayServerAppleEmbedded::get_singleton()) {
@@ -306,6 +328,8 @@ - (void)layoutRenderingLayer {
// MARK: Touches
+#ifndef TVOS_ENABLED
+
- (void)initTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = nullptr;
@@ -467,4 +491,6 @@ - (void)handleMotion {
}
}
+#endif // TVOS_ENABLED
+
@end
diff --git a/drivers/apple_embedded/godot_view_controller.h b/drivers/apple_embedded/godot_view_controller.h
index b5d8e7043fa9..240bd64bbcd8 100644
--- a/drivers/apple_embedded/godot_view_controller.h
+++ b/drivers/apple_embedded/godot_view_controller.h
@@ -30,12 +30,19 @@
#pragma once
+#ifdef TVOS_ENABLED
+#import
+#endif
#import
@class GDTView;
@class GDTKeyboardInputView;
+#ifdef TVOS_ENABLED
+@interface GDTViewController : GCEventViewController
+#else
@interface GDTViewController : UIViewController
+#endif
@property(nonatomic, readonly, strong) GDTView *godotView;
@property(nonatomic, readonly, strong) GDTKeyboardInputView *keyboardView;
diff --git a/drivers/apple_embedded/godot_view_controller.mm b/drivers/apple_embedded/godot_view_controller.mm
index d3831878ae7b..cc777f53f3f1 100644
--- a/drivers/apple_embedded/godot_view_controller.mm
+++ b/drivers/apple_embedded/godot_view_controller.mm
@@ -153,6 +153,10 @@ - (instancetype)initWithCoder:(NSCoder *)coder {
- (void)godot_commonInit {
// Initialize view controller values.
+#ifdef TVOS_ENABLED
+ // Don't pass presses like the B button as regular events.
+ // self.controllerUserInteractionEnabled = NO;
+#endif
}
- (void)didReceiveMemoryWarning {
@@ -163,10 +167,14 @@ - (void)didReceiveMemoryWarning {
- (void)viewDidLoad {
[super viewDidLoad];
+#ifndef TVOS_ENABLED
[self observeKeyboard];
+#endif
[self displayLoadingOverlay];
+#ifndef TVOS_ENABLED
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
+#endif
}
- (void)viewDidAppear:(BOOL)animated {
@@ -182,6 +190,7 @@ - (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
+#ifndef TVOS_ENABLED
- (void)observeKeyboard {
print_verbose("Setting up keyboard input view.");
self.keyboardView = [GDTKeyboardInputView new];
@@ -199,6 +208,7 @@ - (void)observeKeyboard {
name:UIKeyboardDidHideNotification
object:nil];
}
+#endif
- (void)displayLoadingOverlay {
#if !defined(VISIONOS_ENABLED)
@@ -300,6 +310,7 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(idvirtual_keyboard_set_height(0);
}
}
+#endif
@end
diff --git a/drivers/apple_embedded/godot_view_renderer.h b/drivers/apple_embedded/godot_view_renderer.h
index 1d409d9b4500..b74aef6ee70c 100644
--- a/drivers/apple_embedded/godot_view_renderer.h
+++ b/drivers/apple_embedded/godot_view_renderer.h
@@ -30,17 +30,12 @@
#pragma once
-#import
+#import "drivers/apple_embedded/godot_renderer.h"
-@protocol GDTViewRendererProtocol
+#import
-@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
+@interface GDTViewRenderer : GDTRenderer
-- (BOOL)setupView:(UIView *)view;
- (void)renderOnView:(UIView *)view;
@end
-
-@interface GDTViewRenderer : NSObject
-
-@end
diff --git a/drivers/apple_embedded/godot_view_renderer.mm b/drivers/apple_embedded/godot_view_renderer.mm
index 60c67016825a..832eff74dfbe 100644
--- a/drivers/apple_embedded/godot_view_renderer.mm
+++ b/drivers/apple_embedded/godot_view_renderer.mm
@@ -38,82 +38,17 @@
#include "main/main.h"
#include "servers/audio/audio_server.h"
-#import
-#import
-#import
-#import
#import
-@interface GDTViewRenderer ()
-
-@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
-@property(assign, nonatomic) BOOL hasStartedMain;
-@property(assign, nonatomic) BOOL hasFinishedSetup;
-
-@end
-
@implementation GDTViewRenderer
-- (BOOL)setupView:(UIView *)view {
- if (self.hasFinishedSetup) {
- return NO;
- }
-
- if (!OS::get_singleton()) {
- exit(0);
- }
-
- if (!self.hasFinishedProjectDataSetup) {
- [self setupProjectData];
- return YES;
- }
-
- if (!self.hasStartedMain) {
- self.hasStartedMain = YES;
- OS_AppleEmbedded::get_singleton()->start();
- return YES;
- }
-
- self.hasFinishedSetup = YES;
-
- return NO;
-}
-
-- (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 (!OS_AppleEmbedded::get_singleton()) {
- return;
- }
-
- OS_AppleEmbedded::get_singleton()->iterate();
+ safeDispatchSyncToMain(^{
+ if (!OS_AppleEmbedded::get_singleton()) {
+ return;
+ }
+ OS_AppleEmbedded::get_singleton()->iterate();
+ });
}
@end
diff --git a/drivers/apple_embedded/os_apple_embedded.mm b/drivers/apple_embedded/os_apple_embedded.mm
index e624c7e4cddd..6e2427a4a809 100644
--- a/drivers/apple_embedded/os_apple_embedded.mm
+++ b/drivers/apple_embedded/os_apple_embedded.mm
@@ -40,8 +40,8 @@
#include "core/os/os.h"
#include "core/profiling/profiling.h"
#import "drivers/apple/os_log_logger.h"
-#import "drivers/apple_embedded/app_delegate_service.h"
#import "drivers/apple_embedded/display_server_apple_embedded.h"
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_apple_embedded.h"
#import "drivers/apple_embedded/godot_view_controller.h"
#ifdef SDL_ENABLED
diff --git a/drivers/metal/metal3_objects.cpp b/drivers/metal/metal3_objects.cpp
index d0c1f21b74a4..3bab548c4386 100644
--- a/drivers/metal/metal3_objects.cpp
+++ b/drivers/metal/metal3_objects.cpp
@@ -1157,6 +1157,7 @@ void MDCommandBuffer::render_next_subpass() {
}
}
+ desc->setRasterizationRateMap(fb.rasterization_rate_map);
desc->setRenderTargetWidth(MAX((NS::UInteger)MIN(render.render_area.position.x + render.render_area.size.width, fb.size.width), 1u));
desc->setRenderTargetHeight(MAX((NS::UInteger)MIN(render.render_area.position.y + render.render_area.size.height, fb.size.height), 1u));
diff --git a/drivers/metal/metal_objects_shared.h b/drivers/metal/metal_objects_shared.h
index e5d01d904124..b3db7ab2b7ab 100644
--- a/drivers/metal/metal_objects_shared.h
+++ b/drivers/metal/metal_objects_shared.h
@@ -288,6 +288,8 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0), visionos(2.0)) MDFrameBu
public:
Size2i size;
+ MTL::RasterizationRateMap *rasterization_rate_map = nullptr;
+
MDFrameBuffer(Vector p_textures, Size2i p_size) :
textures(p_textures), size(p_size) {}
MDFrameBuffer() {}
diff --git a/drivers/metal/rendering_context_driver_metal.cpp b/drivers/metal/rendering_context_driver_metal.cpp
index 951ff862d57d..c7d55485d5a2 100644
--- a/drivers/metal/rendering_context_driver_metal.cpp
+++ b/drivers/metal/rendering_context_driver_metal.cpp
@@ -40,6 +40,11 @@
#include
#include
+#if defined(VISIONOS_ENABLED)
+#include "modules/visionos_xr/visionos_xr_interface.h"
+#include "platform/visionos/render_mode_visionos.h"
+#endif
+
#pragma mark - Logging
os_log_t LOG_DRIVER;
@@ -62,7 +67,17 @@ Error RenderingContextDriverMetal::initialize() {
capture_available = true;
}
+#if TARGET_OS_VISION
+ RenderModeVisionOS::Mode render_mode = RenderModeVisionOS::get_mode();
+ if (render_mode == RenderModeVisionOS::COMPOSITOR_SERVICES) {
+ metal_device = (MTL::Device *)RenderModeVisionOS::get_compositor_services_device();
+ } else if (render_mode == RenderModeVisionOS::WINDOWED) {
+ metal_device = MTL::CreateSystemDefaultDevice();
+ }
+#else
metal_device = MTL::CreateSystemDefaultDevice();
+#endif
+
#if TARGET_OS_OSX
if (__builtin_available(macOS 13.3, *)) {
metal_device->setShouldMaximizeConcurrentCompilation(true);
@@ -394,14 +409,69 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) SurfaceOffscreen : publi
}
};
+#if TARGET_OS_VISION
+class SurfaceCompositorServices : public RenderingContextDriverMetal::Surface {
+ // Return a dummy framebuffer so present() is called on it, which relays the call to VisionOSXRInterface
+ MDFrameBuffer dummy_framebuffer;
+
+public:
+ SurfaceCompositorServices(MTL::Device *p_device) :
+ Surface(p_device) {
+ dummy_framebuffer.set_texture_count(1);
+ }
+
+ ~SurfaceCompositorServices() override {
+ }
+
+ MTL::PixelFormat get_pixel_format() const override final {
+ // Hardcoded because it's configured in platform/visionos/app_visionos.swift
+ return MTL::PixelFormatRGBA16Float;
+ }
+
+ Error resize(uint32_t p_desired_framebuffer_count, RDD::DataFormat &r_format, RDD::ColorSpace &r_color_space) override final {
+ // Surface cannot be resized in Compositor Services mode
+ return OK;
+ }
+
+ RDD::FramebufferID acquire_next_frame_buffer() override final {
+ return RDD::FramebufferID(&dummy_framebuffer);
+ }
+
+ void present(MTL3::MDCommandBuffer *p_cmd_buffer) override final {
+ Ref visionos_xr_interface = VisionOSXRInterface::find_interface();
+ ERR_FAIL_COND_MSG(!visionos_xr_interface.is_valid(), "visionOS VR interface not found or invalid");
+ visionos_xr_interface->encode_present(p_cmd_buffer);
+ }
+
+ MTL::Drawable *next_drawable() override final {
+ return nullptr;
+ }
+
+ API_AVAILABLE(macos(26.0), ios(26.0))
+ MTL::ResidencySet *get_residency_set() const override final {
+ return nullptr;
+ }
+};
+#endif
+
RenderingContextDriver::SurfaceID RenderingContextDriverMetal::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
- Surface *surface;
+
+ Surface *surface = nullptr;
+#if TARGET_OS_VISION
+ RenderModeVisionOS::Mode render_mode = RenderModeVisionOS::get_mode();
+ if (render_mode == RenderModeVisionOS::COMPOSITOR_SERVICES) {
+ surface = memnew(SurfaceCompositorServices(metal_device));
+ } else if (render_mode == RenderModeVisionOS::WINDOWED) {
+ surface = memnew(SurfaceLayer(wpd->layer, metal_device));
+ }
+#else
if (String v = OS::get_singleton()->get_environment("GODOT_MTL_OFF_SCREEN"); v == U"1") {
surface = memnew(SurfaceOffscreen(wpd->layer, metal_device));
} else {
surface = memnew(SurfaceLayer(wpd->layer, metal_device));
}
+#endif
return SurfaceID(surface);
}
diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h
index 4910c88ee19b..672f9c0ccb85 100644
--- a/drivers/metal/rendering_context_driver_metal.h
+++ b/drivers/metal/rendering_context_driver_metal.h
@@ -107,7 +107,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingContextDriverMe
device(p_device) {}
virtual ~Surface() = default;
- MTL::PixelFormat get_pixel_format() const { return pixel_format; }
+ virtual MTL::PixelFormat get_pixel_format() const { return pixel_format; }
void set_hdr_output_enabled(bool p_enabled) {
if (hdr_output != p_enabled) {
hdr_output = p_enabled;
diff --git a/drivers/metal/rendering_device_driver_metal.cpp b/drivers/metal/rendering_device_driver_metal.cpp
index a914664f3ce9..ab0edb8124e6 100644
--- a/drivers/metal/rendering_device_driver_metal.cpp
+++ b/drivers/metal/rendering_device_driver_metal.cpp
@@ -62,6 +62,9 @@
#include "drivers/metal/rendering_shader_container_metal.h"
#include
+#include
+#include
+#include
#include
#include
@@ -71,6 +74,17 @@
typedef uint64_t MTLGPUAddress;
#endif
+static bool class_conforms_to_protocol_recursive(Class p_class, Protocol *p_protocol) {
+ Class current = p_class;
+ while (current != nil) {
+ if (class_conformsToProtocol(current, p_protocol)) {
+ return true;
+ }
+ current = class_getSuperclass(current);
+ }
+ return false;
+}
+
#pragma mark - Logging
extern os_log_t LOG_DRIVER;
@@ -531,6 +545,12 @@ void RenderingDeviceDriverMetal::texture_free(TextureID p_texture) {
}
uint64_t RenderingDeviceDriverMetal::texture_get_allocation_size(TextureID p_texture) {
+ // p_texture can contain a wrapped MTLRasterizationRateMap as returned by VisionOSXRInterface
+ // so we must check it responds to the allocatedSize selector first
+ Class cls = object_getClass((id)(void *)p_texture.id);
+ if (!class_respondsToSelector(cls, sel_registerName("allocatedSize"))) {
+ return 0;
+ }
MTL::Texture *obj = reinterpret_cast(p_texture.id);
return NS::Object::sendMessageSafe(obj, _MTL_PRIVATE_SEL(allocatedSize));
}
@@ -1008,17 +1028,28 @@ RDD::FramebufferID RenderingDeviceDriverMetal::framebuffer_create(RenderPassID p
Vector textures;
textures.resize(p_attachments.size());
+ MTL::RasterizationRateMap *rasterization_rate_map = nullptr;
for (uint32_t i = 0; i < p_attachments.size(); i += 1) {
MDAttachment const &a = pass->attachments[i];
- MTL::Texture *tex = reinterpret_cast(p_attachments[i].id);
- if (tex == nullptr) {
+ id native_attachment = (id)(void *)p_attachments[i].id;
+ Class cls = object_getClass(native_attachment);
+
+ MTL::Texture *tex = nullptr;
+ bool attachment_is_rasterization_rate_map = false;
+ if (class_conforms_to_protocol_recursive(cls, objc_getProtocol("MTLRasterizationRateMap"))) {
+ rasterization_rate_map = reinterpret_cast(p_attachments[i].id);
+ attachment_is_rasterization_rate_map = true;
+ } else if (class_conforms_to_protocol_recursive(cls, objc_getProtocol("MTLTexture"))) {
+ tex = reinterpret_cast(p_attachments[i].id);
+ }
+ if (tex == nullptr && !attachment_is_rasterization_rate_map) {
#if DEV_ENABLED
WARN_PRINT("Invalid texture for attachment " + itos(i));
#endif
}
if (a.samples > 1) {
- if (tex->sampleCount() != a.samples) {
+ if (tex != nullptr && tex->sampleCount() != a.samples) {
#if DEV_ENABLED
WARN_PRINT("Mismatched sample count for attachment " + itos(i) + "; expected " + itos(a.samples) + ", got " + itos(tex->sampleCount()));
#endif
@@ -1028,6 +1059,7 @@ RDD::FramebufferID RenderingDeviceDriverMetal::framebuffer_create(RenderPassID p
}
MDFrameBuffer *fb = memnew(MDFrameBuffer(textures, Size2i(p_width, p_height)));
+ fb->rasterization_rate_map = rasterization_rate_map;
return FramebufferID(fb);
}
@@ -2679,6 +2711,14 @@ bool RenderingDeviceDriverMetal::has_feature(Features p_feature) {
return true;
case SUPPORTS_POINT_SIZE:
return true;
+ case SUPPORTS_RASTERIZATION_RATE_MAP: {
+ bool is_supported = device->supportsRasterizationRateMap(1);
+#if defined(VISIONOS_ENABLED)
+ // We need to support 2 layers on visionOS. Using more than 2 layers shouldn't be needed.
+ is_supported &= device->supportsRasterizationRateMap(2);
+#endif
+ return is_supported;
+ }
default:
return false;
}
diff --git a/drivers/sdl/SCsub b/drivers/sdl/SCsub
index 4666edb5bf3c..b45181d8fe89 100644
--- a/drivers/sdl/SCsub
+++ b/drivers/sdl/SCsub
@@ -188,9 +188,11 @@ if env["builtin_sdl"]:
"timer/windows/SDL_systimer.c",
]
- elif env["platform"] in ["ios", "visionos"]:
+ elif env["platform"] in ["ios", "tvos", "visionos"]:
if env["platform"] == "ios":
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_IOS"])
+ elif env["platform"] == "tvos":
+ env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_TVOS"])
else:
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_VISIONOS"])
diff --git a/drivers/sdl/SDL_build_config_private.h b/drivers/sdl/SDL_build_config_private.h
index 1a86bb555727..da6706075e40 100644
--- a/drivers/sdl/SDL_build_config_private.h
+++ b/drivers/sdl/SDL_build_config_private.h
@@ -36,6 +36,7 @@
#define HAVE_STDARG_H 1
#define HAVE_STDDEF_H 1
+#define HAVE_STDLIB_H 1
// Here we disable SDL subsystems that are not going to be used
#define SDL_CPUINFO_DISABLED 1
@@ -124,6 +125,7 @@
#define SDL_PLATFORM_UNIX 1
#define HAVE_STDIO_H 1
#define HAVE_LIBC 1
+#define HAVE_MALLOC 1
#define SDL_HAPTIC_IOKIT 1
#define SDL_JOYSTICK_IOKIT 1
#define SDL_JOYSTICK_MFI 1
@@ -132,11 +134,13 @@
#define SDL_THREAD_PTHREAD 1
#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1
-// iOS/visionOS defines
-#elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_VISIONOS)
+// iOS/tvOS/visionOS defines
+#elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) || defined(SDL_PLATFORM_VISIONOS)
#ifdef SDL_PLATFORM_IOS
#define SDL_PLATFORM_PRIVATE_NAME "iOS"
+#elif defined(SDL_PLATFORM_TVOS)
+#define SDL_PLATFORM_PRIVATE_NAME "tvOS"
#else
#define SDL_PLATFORM_PRIVATE_NAME "visionOS"
#endif
@@ -145,6 +149,7 @@
#define HAVE_STDIO_H 1
#define HAVE_LIBC 1
+#define HAVE_MALLOC 1
#define SDL_JOYSTICK_MFI 1
#define SDL_HAPTIC_DUMMY 1
diff --git a/drivers/sdl/joypad_sdl.cpp b/drivers/sdl/joypad_sdl.cpp
index 669d766f7be9..1ab72735cc25 100644
--- a/drivers/sdl/joypad_sdl.cpp
+++ b/drivers/sdl/joypad_sdl.cpp
@@ -63,6 +63,9 @@ JoypadSDL::~JoypadSDL() {
Error JoypadSDL::initialize() {
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
+#ifdef TVOS_ENABLED
+ SDL_SetHint(SDL_HINT_TV_REMOTE_AS_JOYSTICK, "0");
+#endif
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError());
// Add Godot's mapping database from memory
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 1384b02f2f27..e738498a2e86 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -594,7 +594,7 @@ Dictionary OS_Unix::get_memory_info() const {
#if !defined(__GLIBC__) && !defined(WEB_ENABLED)
void OS_Unix::_load_iconv() {
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(APPLE_EMBEDDED_ENABLED)
String iconv_lib_aliases[] = { "/usr/lib/libiconv.2.dylib" };
String iconv_func_aliases[] = { "iconv" };
String charset_lib_aliases[] = { "/usr/lib/libcharset.1.dylib" };
@@ -734,7 +734,9 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List &
}
Dictionary ret;
-#ifdef __EMSCRIPTEN__
+#ifdef APPLE_EMBEDDED_ENABLED
+ ERR_FAIL_V_MSG(ret, "Process execution is not supported on Apple embedded devices.");
+#elif defined(__EMSCRIPTEN__)
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_Web.
ERR_FAIL_V(ret);
@@ -879,7 +881,9 @@ bool OS_Unix::_check_pid_is_running(const pid_t p_pid, int *r_status) const {
}
Error OS_Unix::execute(const String &p_path, const List &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
-#ifdef __EMSCRIPTEN__
+#ifdef APPLE_EMBEDDED_ENABLED
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Process execution is not supported on Apple embedded devices.");
+#elif defined(__EMSCRIPTEN__)
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_Web.
ERR_FAIL_V(ERR_BUG);
@@ -953,7 +957,9 @@ Error OS_Unix::execute(const String &p_path, const List &p_arguments, St
}
Error OS_Unix::create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id, bool p_open_console) {
-#ifdef __EMSCRIPTEN__
+#ifdef APPLE_EMBEDDED_ENABLED
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Process execution is not supported on Apple embedded devices.");
+#elif defined(__EMSCRIPTEN__)
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_Web.
ERR_FAIL_V(ERR_BUG);
diff --git a/editor/export/editor_export_platform_apple_embedded.cpp b/editor/export/editor_export_platform_apple_embedded.cpp
index 0f701c74765f..d3b14c32b297 100644
--- a/editor/export/editor_export_platform_apple_embedded.cpp
+++ b/editor/export/editor_export_platform_apple_embedded.cpp
@@ -1649,6 +1649,8 @@ Error EditorExportPlatformAppleEmbedded::_export_apple_embedded_plugins(const Re
}
Error EditorExportPlatformAppleEmbedded::export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags) {
+ Error result = customize_exported_project(p_preset, p_debug, p_path, p_flags);
+ ERR_FAIL_COND_V_MSG(result != OK, FAILED, "Error customizing export project");
return _export_project_helper(p_preset, p_debug, p_path, p_flags, false);
}
@@ -2478,7 +2480,7 @@ void EditorExportPlatformAppleEmbedded::_check_for_changes_poll_thread(void *ud)
args.push_back(vformat("hardwareProperties.deviceType MATCHES '%s'", device_types));
int ec = 0;
- Error err = OS::get_singleton()->execute("xcrun", args, &devices_json, &ec, true);
+ Error err = OS::get_singleton()->execute("xcrun", args, &devices_json, &ec, false);
if (err == OK && ec == 0) {
Ref json;
json.instantiate();
diff --git a/editor/export/editor_export_platform_apple_embedded.h b/editor/export/editor_export_platform_apple_embedded.h
index 4536ce05d434..057ce6816169 100644
--- a/editor/export/editor_export_platform_apple_embedded.h
+++ b/editor/export/editor_export_platform_apple_embedded.h
@@ -275,6 +275,7 @@ class EditorExportPlatformAppleEmbedded : public EditorExportPlatform {
}
virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags = 0) override;
+ virtual Error customize_exported_project(const Ref &p_preset, bool p_debug, const String &p_path, BitField p_flags = 0) { return OK; } // For inherited platforms to perform additional customization
virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const override;
diff --git a/main/main.cpp b/main/main.cpp
index 68035f50756e..1651ecf8e6c5 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2364,6 +2364,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.linuxbsd", PROPERTY_HINT_ENUM, "vulkan"), "vulkan");
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.android", PROPERTY_HINT_ENUM, "vulkan"), "vulkan");
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.ios", PROPERTY_HINT_ENUM, "metal,vulkan"), "metal");
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.tvos", PROPERTY_HINT_ENUM, "metal"), "metal");
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.visionos", PROPERTY_HINT_ENUM, "metal"), "metal");
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/rendering_device/driver.macos", PROPERTY_HINT_ENUM, "metal,vulkan"), "metal");
@@ -2770,6 +2771,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.linuxbsd", PROPERTY_HINT_ENUM, "default,x11,wayland,headless"), "default");
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.android", PROPERTY_HINT_ENUM, "default,android,headless"), "default");
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.ios", PROPERTY_HINT_ENUM, "default,iOS,headless"), "default");
+ GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.tvos", PROPERTY_HINT_ENUM_SUGGESTION, "default,tvOS,headless"), "default");
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.visionos", PROPERTY_HINT_ENUM, "default,visionOS,headless"), "default");
GLOBAL_DEF_NOVAL(PropertyInfo(Variant::STRING, "display/display_server/driver.macos", PROPERTY_HINT_ENUM, "default,macos,headless"), "default");
diff --git a/methods.py b/methods.py
index 6fe6b2fb7cc6..56e986f0ea6e 100644
--- a/methods.py
+++ b/methods.py
@@ -651,6 +651,14 @@ def detect_darwin_sdk_path(platform, env):
sdk_name = "xrsimulator"
var_name = "APPLE_SDK_PATH"
+ elif platform == "tvos":
+ sdk_name = "appletvos"
+ var_name = "APPLE_SDK_PATH"
+
+ elif platform == "tvosimulator":
+ sdk_name = "appletvsimulator"
+ var_name = "APPLE_SDK_PATH"
+
else:
raise Exception("Invalid platform argument passed to detect_darwin_sdk_path")
@@ -667,7 +675,7 @@ def detect_darwin_sdk_path(platform, env):
def is_apple_clang(env):
import shlex
- if env["platform"] not in ["macos", "ios"]:
+ if env["platform"] not in ["macos", "ios", "tvos"]:
return False
if not using_clang(env):
return False
diff --git a/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist b/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist
index 782d1448dbb5..72b448f7f13d 100644
--- a/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist
+++ b/misc/dist/apple_embedded_xcode/godot_apple_embedded/godot_apple_embedded-Info.plist
@@ -61,6 +61,7 @@
CADisableMinimumFrameDurationOnPhoneUIApplicationSceneManifest
+ $application_scene_manifest_default_session_role
UIApplicationSupportsMultipleScenesUISceneConfigurations
@@ -71,6 +72,7 @@
Default Configuration
+ $application_scene_manifest_immersive_configuration
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 39795755ed19..8f4ead531c93 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -2077,7 +2077,7 @@ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const
p_font_data->face_init = true;
}
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED)
if (p_font_data->font_name == ".Apple Color Emoji UI" || p_font_data->font_name == "Apple Color Emoji") {
// The baseline offset is missing from the Apple Color Emoji UI font data, so add it manually.
// This issue doesn't occur with other system emoji fonts.
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 447fc6278396..5512714c195b 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -994,7 +994,7 @@ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const
p_font_data->face_init = true;
}
-#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
+#if defined(MACOS_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED)
if (p_font_data->font_name == ".Apple Color Emoji UI" || p_font_data->font_name == "Apple Color Emoji") {
// The baseline offset is missing from the Apple Color Emoji UI font data, so add it manually.
// This issue doesn't occur with other system emoji fonts.
diff --git a/modules/visionos_xr/SCsub b/modules/visionos_xr/SCsub
new file mode 100644
index 000000000000..99bb444e910d
--- /dev/null
+++ b/modules/visionos_xr/SCsub
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+from misc.utility.scons_hints import *
+
+Import("env")
+Import("env_modules")
+
+env_visionos_xr = env_modules.Clone()
+env_visionos_xr.add_source_files(env.modules_sources, "*.mm")
diff --git a/modules/visionos_xr/config.py b/modules/visionos_xr/config.py
new file mode 100644
index 000000000000..ea9df7e031eb
--- /dev/null
+++ b/modules/visionos_xr/config.py
@@ -0,0 +1,16 @@
+def can_build(env, platform):
+ return platform == "visionos" and not env["disable_3d"]
+
+
+def configure(env):
+ pass
+
+
+def get_doc_classes():
+ return [
+ "VisionOSXRInterface",
+ ]
+
+
+def get_doc_path():
+ return "doc_classes"
diff --git a/modules/visionos_xr/doc_classes/VisionOSXRInterface.xml b/modules/visionos_xr/doc_classes/VisionOSXRInterface.xml
new file mode 100644
index 000000000000..dbc058e344f6
--- /dev/null
+++ b/modules/visionos_xr/doc_classes/VisionOSXRInterface.xml
@@ -0,0 +1,28 @@
+
+
+
+ visionOS XR implementation for rendering in immersive mode.
+
+
+ This is a visionOS XR implementation for rendering in immersive mode. It uses the Compositor Services framework to render an Immersive scene. It supports the full and mixed immersive modes mode.
+
+ To use this module, you must set the [code]application/app_role[/code] export setting to [code]Immersive[/code]. You can choose the immersion style using the [code]application/immersion_style[/code] export setting.
+ You must use the Metal rendering driver, and only the Mobile renderer is supported for now.
+ You can initialize this interface in the [method Node._ready] method of any of your nodes as follows:
+ [codeblock]
+ func _ready() -> void:
+ var interface = XRServer.find_interface("visionOS")
+ if interface and interface.initialize():
+ var viewport : Viewport = get_viewport()
+ viewport.use_xr = true
+ viewport.vrs_mode = Viewport.VRS_XR
+ viewport.use_hdr_2d = true
+ [/codeblock]
+ Do not initialize the XR interface in the [method Node._process] method, as this will initialize it at some unspecified point in the middle of the frame rendering, possibly causing incorrect visionOS API usage.
+
+
+
+
+
+
+
diff --git a/modules/visionos_xr/register_types.h b/modules/visionos_xr/register_types.h
new file mode 100644
index 000000000000..13acd430d8cc
--- /dev/null
+++ b/modules/visionos_xr/register_types.h
@@ -0,0 +1,40 @@
+/**************************************************************************/
+/* register_types.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef VISIONOS_ENABLED
+
+#include "modules/register_module_types.h"
+
+void initialize_visionos_xr_module(ModuleInitializationLevel p_level);
+void uninitialize_visionos_xr_module(ModuleInitializationLevel p_level);
+
+#endif
diff --git a/modules/visionos_xr/register_types.mm b/modules/visionos_xr/register_types.mm
new file mode 100644
index 000000000000..36195ebca180
--- /dev/null
+++ b/modules/visionos_xr/register_types.mm
@@ -0,0 +1,75 @@
+/**************************************************************************/
+/* register_types.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifdef VISIONOS_ENABLED
+
+#include "register_types.h"
+
+#include "visionos_xr_interface.h"
+
+#include "core/object/class_db.h"
+
+Ref visionos_xr;
+
+void initialize_visionos_xr_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+
+ GDREGISTER_CLASS(VisionOSXRInterface);
+
+ if (XRServer::get_singleton()) {
+ visionos_xr.instantiate();
+ XRServer::get_singleton()->add_interface(visionos_xr);
+ }
+}
+
+void uninitialize_visionos_xr_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+
+ if (visionos_xr.is_valid()) {
+ // uninitialize our interface if it is initialized
+ if (visionos_xr->is_initialized()) {
+ visionos_xr->uninitialize();
+ }
+
+ // unregister our interface from the XR server
+ if (XRServer::get_singleton()) {
+ XRServer::get_singleton()->remove_interface(visionos_xr);
+ }
+
+ // and release
+ visionos_xr.unref();
+ }
+}
+
+#endif // VISIONOS_ENABLED
diff --git a/modules/visionos_xr/visionos_simd_helpers.h b/modules/visionos_xr/visionos_simd_helpers.h
new file mode 100644
index 000000000000..037e64222461
--- /dev/null
+++ b/modules/visionos_xr/visionos_simd_helpers.h
@@ -0,0 +1,108 @@
+/**************************************************************************/
+/* visionos_simd_helpers.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef VISIONOS_ENABLED
+
+#include "core/math/projection.h"
+#include "core/math/transform_3d.h"
+#include "servers/rendering/rendering_device.h"
+
+#import
+#include
+
+namespace MTL {
+
+_FORCE_INLINE_ static Transform3D simd_to_transform3D(const simd_float4x4 &matrix) {
+ Transform3D transform(Vector3(matrix.columns[0].x, matrix.columns[0].y, matrix.columns[0].z),
+ Vector3(matrix.columns[1].x, matrix.columns[1].y, matrix.columns[1].z),
+ Vector3(matrix.columns[2].x, matrix.columns[2].y, matrix.columns[2].z),
+ Vector3(matrix.columns[3].x, matrix.columns[3].y, matrix.columns[3].z));
+ return transform;
+}
+
+_FORCE_INLINE_ static Projection simd_to_projection(const simd_float4x4 &matrix) {
+ Projection projection(Vector4(matrix.columns[0].x, matrix.columns[0].y, matrix.columns[0].z, matrix.columns[0].w),
+ Vector4(matrix.columns[1].x, matrix.columns[1].y, matrix.columns[1].z, matrix.columns[1].w),
+ Vector4(matrix.columns[2].x, matrix.columns[2].y, matrix.columns[2].z, matrix.columns[2].w),
+ Vector4(matrix.columns[3].x, matrix.columns[3].y, matrix.columns[3].z, matrix.columns[3].w));
+ return projection;
+}
+
+_FORCE_INLINE_ static Rect2i rect_from_mtl_viewport(MTLViewport viewport) {
+ return Rect2i(viewport.originX, viewport.originY, viewport.width, viewport.height);
+}
+
+_FORCE_INLINE_ static RenderingDevice::TextureType texture_type_from_metal(MTLTextureType p_type) {
+ switch (p_type) {
+ case MTLTextureType1D:
+ return RenderingDevice::TEXTURE_TYPE_1D;
+ case MTLTextureType2D:
+ return RenderingDevice::TEXTURE_TYPE_2D;
+ case MTLTextureType3D:
+ return RenderingDevice::TEXTURE_TYPE_3D;
+ case MTLTextureTypeCube:
+ return RenderingDevice::TEXTURE_TYPE_CUBE;
+ case MTLTextureType1DArray:
+ return RenderingDevice::TEXTURE_TYPE_1D_ARRAY;
+ case MTLTextureType2DArray:
+ return RenderingDevice::TEXTURE_TYPE_2D_ARRAY;
+ case MTLTextureTypeCubeArray:
+ return RenderingDevice::TEXTURE_TYPE_CUBE_ARRAY;
+ default:
+ return RenderingDevice::TEXTURE_TYPE_MAX; // Fallback for unknown types
+ }
+}
+
+_FORCE_INLINE_ static RenderingDevice::TextureSamples texture_samples_from_metal(int p_sample_count) {
+ switch (p_sample_count) {
+ case 1:
+ return RenderingDevice::TEXTURE_SAMPLES_1;
+ case 2:
+ return RenderingDevice::TEXTURE_SAMPLES_2;
+ case 4:
+ return RenderingDevice::TEXTURE_SAMPLES_4;
+ case 8:
+ return RenderingDevice::TEXTURE_SAMPLES_8;
+ case 16:
+ return RenderingDevice::TEXTURE_SAMPLES_16;
+ case 32:
+ return RenderingDevice::TEXTURE_SAMPLES_32;
+ case 64:
+ return RenderingDevice::TEXTURE_SAMPLES_64;
+ default:
+ return RenderingDevice::TEXTURE_SAMPLES_MAX;
+ }
+}
+
+} //namespace MTL
+
+#endif // VISIONOS_ENABLED
diff --git a/modules/visionos_xr/visionos_xr_interface.h b/modules/visionos_xr/visionos_xr_interface.h
new file mode 100644
index 000000000000..515cb9f73564
--- /dev/null
+++ b/modules/visionos_xr/visionos_xr_interface.h
@@ -0,0 +1,234 @@
+/**************************************************************************/
+/* visionos_xr_interface.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef VISIONOS_ENABLED
+
+#include "core/templates/safe_refcount.h"
+#include "drivers/metal/metal_objects_shared.h"
+#include "drivers/metal/rendering_context_driver_metal.h"
+#include "drivers/metal/rendering_device_driver_metal.h"
+#include "servers/rendering/renderer_compositor.h"
+#include "servers/rendering/rendering_device.h"
+#include "servers/rendering/rendering_server.h"
+#include "servers/xr/xr_interface.h"
+#include "servers/xr/xr_positional_tracker.h"
+#include "servers/xr/xr_vrs.h"
+
+#ifdef __OBJC__
+// When compiling as Objective-C++, include the actual headers
+#import
+#import
+#else
+// When compiling as C++, use forward declarations for ARKit and CompositorServices types (opaque pointers)
+typedef struct ar_world_tracking_provider *ar_world_tracking_provider_t;
+typedef struct cp_layer_renderer *cp_layer_renderer_t;
+typedef struct cp_layer_renderer_capabilities *cp_layer_renderer_capabilities_t;
+typedef struct ar_session *ar_session_t;
+typedef struct ar_device_anchor *ar_device_anchor_t;
+typedef struct cp_frame *cp_frame_t;
+typedef struct cp_drawable *cp_drawable_t;
+#endif
+
+class VisionOSXRInterface : public XRInterface {
+ GDCLASS(VisionOSXRInterface, XRInterface);
+
+public:
+ enum SignalEnum {
+ VISIONOS_XR_SIGNAL_SESSION_STARTED,
+ VISIONOS_XR_SIGNAL_SESSION_PAUSED,
+ VISIONOS_XR_SIGNAL_SESSION_RESUMED,
+ VISIONOS_XR_SIGNAL_SESSION_INVALIDATED,
+ VISIONOS_XR_SIGNAL_POSE_RECENTERED,
+ VISIONOS_XR_SIGNAL_MAX,
+ };
+
+private:
+ bool initialized = false;
+ XRInterface::TrackingStatus tracking_state;
+
+ static RenderingServer *rendering_server;
+ static ar_world_tracking_provider_t world_tracking_provider;
+
+ cp_layer_renderer_t layer_renderer = nullptr;
+ cp_layer_renderer_capabilities_t layer_renderer_capabilities = nullptr;
+ ar_session_t ar_session = nullptr;
+
+ ar_device_anchor_t current_device_anchor = nullptr;
+ cp_frame_t current_frame = nullptr;
+
+ // Data and functions only accessible from the rendering thread
+ class RenderThread : public Object {
+ // Inherit from Object to use callable_mp(), so we declare it as GDCLASS,
+ // but this class should not be exposed to GDScript with GDREGISTER_CLASS.
+ GDCLASS(RenderThread, Object);
+
+ private:
+ bool initialized = false;
+ RenderingDevice *rendering_device = nullptr;
+ PixelFormats *pixel_formats = nullptr;
+
+ float minimum_supported_near_plane = 0;
+
+ // RenderThread must query the device anchor again,
+ // because ar_device_anchor_t objects cannot be safely shared between threads
+ ar_device_anchor_t current_device_anchor = nullptr;
+ Transform3D origin_from_head;
+
+ cp_frame_t current_frame = nullptr;
+ cp_drawable_t current_drawable = nullptr;
+
+ RD::Texture current_color_texture;
+ RID current_color_texture_id;
+ RD::Texture current_depth_texture;
+ RID current_depth_texture_id;
+ RD::Texture current_rasterization_rate_map;
+ RID current_rasterization_rate_map_id;
+
+ // Cached render target size, set in pre_render() on the render thread
+ // and read from the game thread via get_render_target_size().
+ SafeNumeric cached_render_target_width{ 0 };
+ SafeNumeric cached_render_target_height{ 0 };
+
+ public:
+ void initialize();
+ void uninitialize();
+
+ void set_minimum_supported_near_plane(float p_minimum_supported_near_plane);
+ // p_current_frame should be an cp_frame_t pointer casted to uint64_t
+ void set_current_frame(uint64_t p_current_frame);
+
+ // Safe to be called from the game thread
+ void start_frame_update();
+ void end_frame_update();
+ Size2 get_render_target_size();
+
+ // Only safe to be called from the render thread
+ uint32_t get_view_count();
+ Transform3D get_camera_transform();
+ Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform);
+ Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far);
+ Rect2i get_render_region();
+
+ void pre_render();
+ Vector post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect);
+ void encode_present(MTL3::MDCommandBuffer *p_cmd_buffer);
+ void end_frame();
+
+ RID get_color_texture();
+ RID get_depth_texture();
+ RID get_vrs_texture();
+ } rt;
+
+ // Head tracker
+ Ref head_tracker;
+
+ static void _bind_methods();
+ static const String name;
+ static StringName get_signal_name(SignalEnum p_signal);
+
+ void set_head_pose_from_arkit();
+
+public:
+ static Ref find_interface() {
+ return XRServer::get_singleton()->find_interface(name);
+ }
+
+ VisionOSXRInterface();
+ ~VisionOSXRInterface();
+
+ void emit_signal_enum(SignalEnum p_signal);
+
+ virtual StringName get_name() const override;
+ virtual uint32_t get_capabilities() const override;
+
+ virtual TrackingStatus get_tracking_status() const override;
+
+ virtual bool is_initialized() const override;
+ virtual bool initialize() override;
+ virtual void uninitialize() override;
+
+ // The LayerRenderer and Capabilities are polled from the app delegate when initializing the VisionOSXRInterface,
+ // but they need to be updated when the app backgrounds and foregrounds because they are recreated by visionOS
+ void update_layer_renderer(cp_layer_renderer_t p_layer_renderer, cp_layer_renderer_capabilities_t p_layer_renderer_capabilities);
+
+ virtual Dictionary get_system_info() override;
+ virtual VRSTextureFormat get_vrs_texture_format() override;
+
+ virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
+ virtual XRInterface::PlayAreaMode get_play_area_mode() const override;
+ virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override;
+
+ // Methods called from the game thread
+ virtual void process() override;
+ virtual Size2 get_render_target_size() override;
+
+ // Methods only called from the render thread
+ virtual uint32_t get_view_count() override {
+ return rt.get_view_count();
+ }
+ virtual Transform3D get_camera_transform() override {
+ return rt.get_camera_transform();
+ }
+ virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override {
+ return rt.get_transform_for_view(p_view, p_cam_transform);
+ }
+ virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override {
+ return rt.get_projection_for_view(p_view, p_aspect, p_z_near, p_z_far);
+ }
+ virtual Rect2i get_render_region() override {
+ return rt.get_render_region();
+ }
+ virtual void pre_render() override {
+ rt.pre_render();
+ }
+ virtual Vector post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override {
+ return rt.post_draw_viewport(p_render_target, p_screen_rect);
+ }
+ void encode_present(MTL3::MDCommandBuffer *p_cmd_buffer) {
+ rt.encode_present(p_cmd_buffer);
+ }
+ virtual void end_frame() override {
+ rt.end_frame();
+ }
+
+ virtual RID get_color_texture() override {
+ return rt.get_color_texture();
+ }
+ virtual RID get_depth_texture() override {
+ return rt.get_depth_texture();
+ }
+ virtual RID get_vrs_texture() override {
+ return rt.get_vrs_texture();
+ }
+};
+
+#endif
diff --git a/modules/visionos_xr/visionos_xr_interface.mm b/modules/visionos_xr/visionos_xr_interface.mm
new file mode 100644
index 000000000000..77ef6286fd0f
--- /dev/null
+++ b/modules/visionos_xr/visionos_xr_interface.mm
@@ -0,0 +1,620 @@
+/**************************************************************************/
+/* visionos_xr_interface.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifdef VISIONOS_ENABLED
+
+#include "visionos_xr_interface.h"
+
+#include "core/input/input.h"
+#include "core/object/callable_mp.h"
+#include "core/object/class_db.h"
+#include "core/os/os.h"
+#include "core/os/thread.h"
+#include "drivers/metal/metal3_objects.h"
+#include "servers/rendering/rendering_device.h"
+#include "servers/rendering/rendering_server.h" // ERR_NOT_ON_RENDER_THREAD_V
+#include "servers/rendering/rendering_server_globals.h"
+#include "servers/rendering/rendering_server_types.h"
+
+#include "modules/visionos_xr/visionos_simd_helpers.h"
+#include "platform/visionos/godot_app_delegate_service_visionos.h"
+
+#import
+#import
+
+const String VisionOSXRInterface::name = "visionOS";
+
+RenderingServer *VisionOSXRInterface::rendering_server = nullptr;
+ar_world_tracking_provider_t VisionOSXRInterface::world_tracking_provider = nullptr;
+
+StringName VisionOSXRInterface::get_signal_name(SignalEnum p_signal) {
+ switch (p_signal) {
+ case VISIONOS_XR_SIGNAL_SESSION_STARTED:
+ return SNAME("session_started");
+ break;
+ case VISIONOS_XR_SIGNAL_SESSION_PAUSED:
+ return SNAME("session_paused");
+ break;
+ case VISIONOS_XR_SIGNAL_SESSION_RESUMED:
+ return SNAME("session_resumed");
+ break;
+ case VISIONOS_XR_SIGNAL_SESSION_INVALIDATED:
+ return SNAME("session_invalidated");
+ break;
+ case VISIONOS_XR_SIGNAL_POSE_RECENTERED:
+ return SNAME("pose_recentered");
+ break;
+ default:
+ return "";
+ break;
+ }
+}
+
+void VisionOSXRInterface::emit_signal_enum(SignalEnum p_signal) {
+ emit_signal(get_signal_name(p_signal));
+}
+
+void VisionOSXRInterface::_bind_methods() {
+ // Signals
+ for (int i = 0; i < VISIONOS_XR_SIGNAL_MAX; i++) {
+ ADD_SIGNAL(MethodInfo(get_signal_name((SignalEnum)i)));
+ }
+}
+
+VisionOSXRInterface::VisionOSXRInterface() {}
+
+VisionOSXRInterface::~VisionOSXRInterface() {
+ if (is_initialized()) {
+ uninitialize();
+ }
+}
+
+StringName VisionOSXRInterface::get_name() const {
+ return VisionOSXRInterface::name;
+}
+
+uint32_t VisionOSXRInterface::get_capabilities() const {
+ return XRInterface::XR_VR + XRInterface::XR_AR + XRInterface::XR_STEREO;
+}
+
+XRInterface::TrackingStatus VisionOSXRInterface::get_tracking_status() const {
+ return tracking_state;
+}
+
+bool VisionOSXRInterface::is_initialized() const {
+ return (initialized);
+}
+
+bool VisionOSXRInterface::initialize() {
+ ERR_FAIL_COND_V_MSG(initialized, true, "VisionOSXRInterface already initialized.");
+
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL_V(xr_server, false);
+
+ String driver_name = OS::get_singleton()->get_current_rendering_driver_name().to_lower();
+ ERR_FAIL_COND_V_MSG(driver_name != "metal", false, "The visionOS XR interface requires the Metal rendering driver.");
+
+ GDTRenderMode app_delegate_render_mode = GDTAppDelegateServiceVisionOS.renderMode;
+ ERR_FAIL_COND_V_MSG(app_delegate_render_mode != GDTRenderModeCompositorServices, false, "The visionOS XR interface requires GDTRenderModeCompositorServices render mode.");
+
+ layer_renderer = GDTAppDelegateServiceVisionOS.layerRenderer;
+ layer_renderer_capabilities = GDTAppDelegateServiceVisionOS.layerRendererCapabilities;
+
+ ERR_FAIL_NULL_V_MSG(layer_renderer, false, "GDTAppDelegateServiceVisionOS.layerRenderer not set");
+ ERR_FAIL_NULL_V_MSG(layer_renderer_capabilities, false, "GDTAppDelegateServiceVisionOS.layerRendererCapabilities not set");
+
+ // ARKit session initialization
+ ar_session = ar_session_create();
+ ar_world_tracking_configuration_t world_tracking_configuration = ar_world_tracking_configuration_create();
+ world_tracking_provider = ar_world_tracking_provider_create(world_tracking_configuration);
+ current_device_anchor = ar_device_anchor_create();
+ ar_data_providers_t data_providers = ar_data_providers_create();
+ ar_data_providers_add_data_provider(data_providers, world_tracking_provider);
+ ar_session_run(ar_session, data_providers);
+
+ // Head tracker initialization
+ head_tracker.instantiate();
+ head_tracker->set_tracker_type(XRServer::TRACKER_HEAD);
+ head_tracker->set_tracker_name("head");
+ head_tracker->set_tracker_desc("Device head pose");
+ xr_server->add_tracker(head_tracker);
+
+ // RenderThread
+ rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL_V(rendering_server, false);
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::initialize));
+
+ float minimum_supported_near_plane = cp_layer_renderer_capabilities_supported_minimum_near_plane_distance(layer_renderer_capabilities);
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::set_minimum_supported_near_plane).bind(minimum_supported_near_plane));
+
+ // Make this our primary interface
+ xr_server->set_primary_interface(this);
+
+ initialized = true;
+ return initialized;
+}
+
+void VisionOSXRInterface::uninitialize() {
+ if (!initialized) {
+ return;
+ }
+
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::uninitialize));
+
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server != nullptr) {
+ if (head_tracker.is_valid()) {
+ xr_server->remove_tracker(head_tracker);
+ head_tracker.unref();
+ }
+
+ if (xr_server->get_primary_interface() == this) {
+ // no longer our primary interface
+ xr_server->set_primary_interface(nullptr);
+ }
+
+ initialized = false;
+ }
+}
+
+void VisionOSXRInterface::RenderThread::initialize() {
+ ERR_NOT_ON_RENDER_THREAD;
+ rendering_device = RenderingDevice::get_singleton();
+ RenderingDeviceDriverMetal *rendering_device_driver_metal = (RenderingDeviceDriverMetal *)rendering_device->get_device_driver();
+ pixel_formats = &rendering_device_driver_metal->get_pixel_formats();
+
+ current_device_anchor = ar_device_anchor_create();
+
+ initialized = true;
+}
+
+void VisionOSXRInterface::RenderThread::uninitialize() {
+ ERR_NOT_ON_RENDER_THREAD;
+ if (current_color_texture_id != RID()) {
+ rendering_device->texture_owner.free(current_color_texture_id);
+ }
+ if (current_depth_texture_id != RID()) {
+ rendering_device->texture_owner.free(current_depth_texture_id);
+ }
+ if (current_rasterization_rate_map_id != RID()) {
+ rendering_device->texture_owner.free(current_rasterization_rate_map_id);
+ }
+ initialized = false;
+}
+
+void VisionOSXRInterface::update_layer_renderer(cp_layer_renderer_t p_layer_renderer, cp_layer_renderer_capabilities_t p_layer_renderer_capabilities) {
+ layer_renderer = p_layer_renderer;
+ layer_renderer_capabilities = p_layer_renderer_capabilities;
+
+ float minimum_supported_near_plane = cp_layer_renderer_capabilities_supported_minimum_near_plane_distance(layer_renderer_capabilities);
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::set_minimum_supported_near_plane).bind(minimum_supported_near_plane));
+}
+
+Dictionary VisionOSXRInterface::get_system_info() {
+ Dictionary dict;
+
+ dict[SNAME("XRRuntimeName")] = String("Godot visionOS XR interface");
+ dict[SNAME("XRRuntimeVersion")] = String("1.0");
+
+ return dict;
+}
+
+VisionOSXRInterface::VRSTextureFormat VisionOSXRInterface::get_vrs_texture_format() {
+ return XR_VRS_TEXTURE_FORMAT_RASTERIZATION_RATE_MAP;
+}
+
+bool VisionOSXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) {
+ return p_mode == XR_PLAY_AREA_ROOMSCALE;
+}
+
+XRInterface::PlayAreaMode VisionOSXRInterface::get_play_area_mode() const {
+ return XR_PLAY_AREA_ROOMSCALE;
+}
+
+bool VisionOSXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) {
+ return p_mode == XR_PLAY_AREA_ROOMSCALE;
+}
+
+void VisionOSXRInterface::set_head_pose_from_arkit() {
+ ERR_FAIL_NULL_MSG(current_frame, "Current frame is nil, process() has probably not been called, using identity transform.");
+
+ cp_frame_timing_t frame_timing = cp_frame_predict_timing(current_frame);
+
+ CFTimeInterval presentation_time = cp_time_to_cf_time_interval(cp_frame_timing_get_presentation_time(frame_timing));
+ ar_device_anchor_query_status_t query_anchor_result = ar_world_tracking_provider_query_device_anchor_at_timestamp(world_tracking_provider, presentation_time, current_device_anchor);
+
+ if (query_anchor_result != ar_device_anchor_query_status_success) {
+ tracking_state = XRInterface::XR_NOT_TRACKING;
+ ERR_FAIL_MSG("cannot query device anchor, result: " + itos(query_anchor_result));
+ }
+
+ simd_float4x4 origin_from_head_simd = ar_anchor_get_origin_from_anchor_transform(current_device_anchor);
+ tracking_state = XRInterface::XR_NORMAL_TRACKING;
+
+ if (head_tracker.is_valid()) {
+ // Set our head position (in real space, reference frame and world scale is applied later)
+ head_tracker->set_pose("default", MTL::simd_to_transform3D(origin_from_head_simd), Vector3(), Vector3(), XRPose::XR_TRACKING_CONFIDENCE_HIGH);
+ }
+}
+
+void VisionOSXRInterface::process() {
+ if (!initialized) {
+ return;
+ }
+
+ current_frame = cp_layer_renderer_query_next_frame(layer_renderer);
+
+ ERR_FAIL_NULL_MSG(current_frame, "Layer renderer unexpectedly returned a nil frame, the layer renderer has probably been invalidated and it hasn't been updated to a new one.");
+
+ // Set head pose before engine update, so scripts can access fresh head tracker data
+ set_head_pose_from_arkit();
+
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::set_current_frame).bind((uint64_t)current_frame));
+ rendering_server->call_on_render_thread(callable_mp(&rt, &RenderThread::start_frame_update));
+}
+
+Size2 VisionOSXRInterface::get_render_target_size() {
+ return rt.get_render_target_size();
+}
+
+void VisionOSXRInterface::RenderThread::set_minimum_supported_near_plane(float p_minimum_supported_near_plane) {
+ ERR_NOT_ON_RENDER_THREAD;
+ minimum_supported_near_plane = p_minimum_supported_near_plane;
+}
+
+void VisionOSXRInterface::RenderThread::set_current_frame(uint64_t p_current_frame) {
+ ERR_NOT_ON_RENDER_THREAD;
+ current_frame = (cp_frame_t)p_current_frame;
+
+ // Query anchor again from the render thread
+ cp_frame_timing_t current_timing = cp_frame_predict_timing(current_frame);
+ CFTimeInterval presentation_time = cp_time_to_cf_time_interval(cp_frame_timing_get_presentation_time(current_timing));
+ ar_device_anchor_query_status_t query_anchor_result = ar_world_tracking_provider_query_device_anchor_at_timestamp(world_tracking_provider, presentation_time, current_device_anchor);
+
+ if (query_anchor_result != ar_device_anchor_query_status_success) {
+ ERR_FAIL_MSG("Cannot query device anchor, result: " + itos(query_anchor_result));
+ }
+
+ simd_float4x4 origin_from_head_simd = ar_anchor_get_origin_from_anchor_transform(current_device_anchor);
+ origin_from_head = MTL::simd_to_transform3D(origin_from_head_simd);
+}
+
+uint32_t VisionOSXRInterface::RenderThread::get_view_count() {
+ // No need for ERR_NOT_ON_RENDER_THREAD
+ return 2;
+}
+
+Transform3D VisionOSXRInterface::RenderThread::get_camera_transform() {
+ Transform3D camera_transform;
+ ERR_NOT_ON_RENDER_THREAD_V(camera_transform);
+
+ if (!initialized) {
+ return camera_transform;
+ }
+
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL_V(xr_server, camera_transform);
+ // scale our origin point of our transform
+ float world_scale = xr_server->get_world_scale();
+ origin_from_head.origin *= world_scale;
+ camera_transform = origin_from_head;
+ return camera_transform;
+}
+
+Transform3D VisionOSXRInterface::RenderThread::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
+ Transform3D origin_from_eye;
+ ERR_NOT_ON_RENDER_THREAD_V(origin_from_eye);
+
+ XRServer *xr_server = XRServer::get_singleton();
+ ERR_FAIL_NULL_V(xr_server, origin_from_eye);
+ if (initialized) {
+ ERR_FAIL_COND_V(p_view > get_view_count(), origin_from_eye);
+ ERR_FAIL_NULL_V_MSG(current_drawable, origin_from_eye, "Current drawable is nil, pre_render() has probably not been called, using identity transform.");
+
+ cp_view_t view = cp_drawable_get_view(current_drawable, p_view);
+ simd_float4x4 head_from_eye_simd = cp_view_get_transform(view);
+ Transform3D head_from_eye = MTL::simd_to_transform3D(head_from_eye_simd);
+
+ origin_from_eye = origin_from_head * head_from_eye;
+
+ // Scale origin point by XROrigin3D's World Scale attribute
+ float world_scale = xr_server->get_world_scale();
+ origin_from_eye.origin *= world_scale;
+ } else {
+ ERR_PRINT("vision_vr_interface not initialized, returning received camera transform");
+ origin_from_eye = Transform3D();
+ }
+ Transform3D reference_frame = xr_server->get_reference_frame();
+ return p_cam_transform * reference_frame * origin_from_eye;
+}
+
+Projection VisionOSXRInterface::RenderThread::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
+ Projection eye_projection;
+ ERR_NOT_ON_RENDER_THREAD_V(eye_projection);
+
+ if (!initialized) {
+ return eye_projection;
+ }
+
+ ERR_FAIL_COND_V(p_view > get_view_count(), eye_projection);
+ ERR_FAIL_NULL_V_MSG(current_drawable, eye_projection, "Current drawable is nil, pre_render() has probably not been called.");
+
+ XRServer *xr_server = XRServer::get_singleton();
+ float world_scale = xr_server->get_world_scale();
+
+ double scaled_z_far = p_z_far / world_scale;
+ double scaled_z_near = p_z_near / world_scale;
+
+ ERR_FAIL_COND_V_MSG(scaled_z_near < minimum_supported_near_plane, eye_projection, "Your XRCamera3D Near value is lower than the minimum value supported by the visionOS platform. Make sure that Near divided by XROrigin's World Scale is higher than or equal to the value returned by LayerRender.Capabilities.supportedMinimumNearPlaneDistance. This value is 0.1 for Apple Vision Pro.");
+
+ simd_float2 depth_range = simd_make_float2(scaled_z_far, scaled_z_near);
+ cp_drawable_set_depth_range(current_drawable, depth_range);
+ simd_float4x4 eye_simd_projection = cp_drawable_compute_projection(current_drawable, cp_axis_direction_convention_right_up_forward, p_view);
+ eye_projection = MTL::simd_to_projection(eye_simd_projection);
+
+ // Godot renderers work in the normalized [-1, 1] depth space, and they do a final z remap of the projection matrixes to the [0, 1] depth space in RenderSceneDataRD::update_ubo().
+ // Compositor Services projection matrices are already in the [0, 1] depth space, so we need to apply the inverse z remap before passing them to the renderer.
+ Projection normalized_depth_correction;
+ normalized_depth_correction.set_depth_correction(false, false, true);
+
+ // Correct depth by world_scale
+ Projection reverse_z;
+ real_t *m = &reverse_z.columns[0][0];
+ m[10] = -1.0;
+ m[14] = 1.0;
+
+ Projection world_scale_correction;
+ world_scale_correction.make_scale(Vector3(1, 1, world_scale));
+
+ eye_projection = normalized_depth_correction.inverse() * reverse_z.inverse() * world_scale_correction * reverse_z * eye_projection;
+ return eye_projection;
+}
+
+// The render region is the logical texture size. With foveated rendering, it's bigger than the
+// physical texture size. This value is equivalent to rasterizationRateMap.screenSize.
+Rect2i VisionOSXRInterface::RenderThread::get_render_region() {
+ Rect2 viewport_rect;
+
+ ERR_NOT_ON_RENDER_THREAD_V(viewport_rect);
+
+ if (!initialized) {
+ return viewport_rect;
+ }
+
+ ERR_FAIL_NULL_V_MSG(current_drawable, viewport_rect, "Current drawable is nil, pre_render() has probably not been called.");
+
+ // The viewport should be the same for both eyes, so only get it from the first view
+ cp_view_t view = cp_drawable_get_view(current_drawable, 0);
+ cp_view_texture_map_t view_texture_map = cp_view_get_view_texture_map(view);
+ MTLViewport viewport = cp_view_texture_map_get_viewport(view_texture_map);
+ viewport_rect = MTL::rect_from_mtl_viewport(viewport);
+ return viewport_rect;
+}
+
+Size2 VisionOSXRInterface::RenderThread::get_render_target_size() {
+ // Read atomic values cached by pre_render().
+ return Size2(cached_render_target_width.get(), cached_render_target_height.get());
+}
+
+void VisionOSXRInterface::RenderThread::start_frame_update() {
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!initialized) {
+ return;
+ }
+
+ ERR_FAIL_NULL_MSG(current_frame, "Current frame is nil, process() has probably not been called.");
+ cp_frame_start_update(current_frame);
+}
+
+void VisionOSXRInterface::RenderThread::end_frame_update() {
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!initialized) {
+ return;
+ }
+
+ ERR_FAIL_NULL_MSG(current_frame, "Current frame is nil, process() has probably not been called.");
+ cp_frame_end_update(current_frame);
+}
+
+void VisionOSXRInterface::RenderThread::pre_render() {
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!initialized) {
+ return;
+ }
+
+ end_frame_update();
+
+ cp_frame_timing_t timing = cp_frame_predict_timing(current_frame);
+ cp_time_wait_until(cp_frame_timing_get_optimal_input_time(timing));
+
+ cp_frame_start_submission(current_frame);
+ cp_drawable_array_t drawables = cp_frame_query_drawables(current_frame);
+ size_t drawable_count = cp_drawable_array_get_count(drawables);
+
+ for (size_t i = 0; i < drawable_count; i++) {
+ cp_drawable_t drawable = cp_drawable_array_get_drawable(drawables, i);
+ // Find screen drawable (target = cp_drawable_target_built_in).
+ // High quality recording (target = cp_drawable_target_capture) not supported yet,
+ // to support this feature, we'd need Godot to perform an additional render pass on the extra drawable
+ if (cp_drawable_get_target(drawable) == cp_drawable_target_built_in) {
+ current_drawable = drawable;
+ }
+ }
+ ERR_FAIL_NULL_MSG(current_drawable, "Built-in drawable not found, aborting");
+
+ // Cache the render target size so it can be read from the game thread.
+ id color_texture = cp_drawable_get_color_texture(current_drawable, 0);
+ cached_render_target_width.set(color_texture.width);
+ cached_render_target_height.set(color_texture.height);
+
+ if (current_device_anchor != nil) {
+ cp_drawable_set_device_anchor(current_drawable, current_device_anchor);
+ } else {
+ ERR_PRINT("Current device anchor is nil, will present drawable without a device anchor");
+ }
+}
+
+Vector VisionOSXRInterface::RenderThread::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
+ ERR_NOT_ON_RENDER_THREAD_V(Vector());
+
+ if (!initialized) {
+ return Vector();
+ }
+
+ // We're overriding the color and depth textures, no need for screen blits, return empty BlitToScreen vector
+ // However, we need to acquire the dummy frame buffer
+ RD::get_singleton()->screen_prepare_for_drawing(DisplayServerEnums::MAIN_WINDOW_ID);
+ return Vector();
+}
+
+void VisionOSXRInterface::RenderThread::encode_present(MTL3::MDCommandBuffer *p_cmd_buffer) {
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!initialized) {
+ return;
+ }
+
+ ERR_FAIL_NULL_MSG(current_drawable, "Current drawable is nil, process() has probably not been called.");
+ cp_drawable_encode_present(current_drawable, (__bridge id)p_cmd_buffer->get_command_buffer());
+ current_drawable = nullptr;
+}
+
+void VisionOSXRInterface::RenderThread::end_frame() {
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!initialized) {
+ return;
+ }
+
+ ERR_FAIL_NULL_MSG(current_frame, "Current frame is nil, process() has probably not been called.");
+ cp_frame_end_submission(current_frame);
+ current_frame = nullptr;
+}
+
+RID VisionOSXRInterface::RenderThread::get_color_texture() {
+ ERR_NOT_ON_RENDER_THREAD_V(RID());
+
+ if (!initialized) {
+ return RID();
+ }
+
+ if (current_color_texture_id != RID()) {
+ rendering_device->texture_owner.free(current_color_texture_id);
+ }
+
+ ERR_FAIL_NULL_V_MSG(current_drawable, RID(), "Current drawable is nil, pre_render() has probably not been called.");
+
+ id color_texture = cp_drawable_get_color_texture(current_drawable, 0);
+ current_color_texture_id = rendering_device->texture_create_from_extension(
+ MTL::texture_type_from_metal(color_texture.textureType),
+ pixel_formats->getDataFormat((MTL::PixelFormat)color_texture.pixelFormat),
+ MTL::texture_samples_from_metal(color_texture.sampleCount),
+ RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT,
+ (uint64_t)color_texture,
+ color_texture.width,
+ color_texture.height,
+ color_texture.depth,
+ color_texture.arrayLength,
+ color_texture.mipmapLevelCount);
+
+ return current_color_texture_id;
+}
+
+RID VisionOSXRInterface::RenderThread::get_depth_texture() {
+ ERR_NOT_ON_RENDER_THREAD_V(RID());
+
+ if (!initialized) {
+ return RID();
+ }
+
+ if (current_depth_texture_id != RID()) {
+ rendering_device->texture_owner.free(current_depth_texture_id);
+ }
+
+ ERR_FAIL_NULL_V_MSG(current_drawable, RID(), "Current drawable is nil, pre_render() has probably not been called.");
+ id depth_texture = cp_drawable_get_depth_texture(current_drawable, 0);
+
+ current_depth_texture_id = rendering_device->texture_create_from_extension(
+ MTL::texture_type_from_metal(depth_texture.textureType),
+ pixel_formats->getDataFormat((MTL::PixelFormat)depth_texture.pixelFormat),
+ MTL::texture_samples_from_metal(depth_texture.sampleCount),
+ RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT,
+ (uint64_t)depth_texture,
+ depth_texture.width,
+ depth_texture.height,
+ depth_texture.depth,
+ depth_texture.arrayLength,
+ depth_texture.mipmapLevelCount);
+
+ return current_depth_texture_id;
+}
+
+RID VisionOSXRInterface::RenderThread::get_vrs_texture() {
+ ERR_NOT_ON_RENDER_THREAD_V(RID());
+
+ if (!initialized) {
+ return RID();
+ }
+
+ if (current_rasterization_rate_map_id != RID()) {
+ rendering_device->texture_owner.free(current_rasterization_rate_map_id);
+ }
+
+ ERR_FAIL_NULL_V_MSG(current_drawable, RID(), "Current drawable is nil, pre_render() has probably not been called.");
+ size_t count = cp_drawable_get_rasterization_rate_map_count(current_drawable);
+ ERR_FAIL_COND_V_MSG(count == 0, RID(), "No rasterizationRateMaps found");
+ id rasterization_rate_map = cp_drawable_get_rasterization_rate_map(current_drawable, 0);
+ MTLSize logical_size = rasterization_rate_map.screenSize;
+
+ RD::Texture texture;
+ texture.driver_id = RDD::TextureID((__bridge void *)rasterization_rate_map);
+ texture.usage_flags = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT;
+ texture.width = logical_size.width;
+ texture.height = logical_size.height;
+ texture.layers = rasterization_rate_map.layerCount;
+ // The following spoofed values are unused, but they are required
+ // to pass RenderingDevice::_render_pass_create() validation
+ texture.type = RDD::TEXTURE_TYPE_2D_ARRAY;
+ texture.format = RDD::DATA_FORMAT_R8_UINT;
+ texture.samples = RDD::TEXTURE_SAMPLES_1;
+ texture.depth = 1;
+ texture.mipmaps = 1;
+ ERR_FAIL_COND_V(!texture.driver_id, RID());
+
+ current_rasterization_rate_map = texture;
+ current_rasterization_rate_map_id = rendering_device->texture_owner.make_rid(current_rasterization_rate_map);
+
+ return current_rasterization_rate_map_id;
+}
+
+#endif // VISIONOS_ENABLED
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index 92f6c1714bb4..204a257e1beb 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -2,26 +2,41 @@
from misc.utility.scons_hints import *
from platform_ios_builders import generate_bundle
+from SCons.Script import Glob
-from platform_methods import combine_libs_apple_embedded
+from platform_methods import combine_libs_apple_embedded, setup_swift_builder
Import("env")
-ios_lib = [
- "device_metrics.mm",
- "display_layer_ios.mm",
- "display_server_ios.mm",
- "godot_view_ios.mm",
- "main_ios.mm",
- "os_ios.mm",
-]
+swift_files = Glob("*.swift")
+swift_file_names = list(map(lambda f: f.name, swift_files))
+
+mm_files = Glob("*.mm")
+mm_file_names = list(map(lambda f: f.name, mm_files))
+
+ios_lib = swift_file_names + mm_file_names
env_ios = env.Clone()
-ios_lib = env_ios.add_library("ios", ios_lib)
-# (iOS) Enable module support
+# Configure Swift builder
+additional_swift_file_names = ["../../drivers/apple_embedded/godot_swiftui_view_controller.swift"]
+apple_platform = env["APPLE_PLATFORM"]
+sdk_path = env["APPLE_SDK_PATH"]
+current_path = Dir(".").abspath
+bridging_header_filename = "bridging_header_ios.h"
+setup_swift_builder(
+ env_ios,
+ apple_platform,
+ sdk_path,
+ current_path,
+ bridging_header_filename,
+ swift_file_names + additional_swift_file_names,
+)
+
+# Enable module support
env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+ios_lib = env_ios.add_library("ios", ios_lib)
if "LIBS_EXTERNAL" in env_ios:
env_ios.Depends(ios_lib, env_ios["LIBS_EXTERNAL"])
diff --git a/platform/ios/app_ios.swift b/platform/ios/app_ios.swift
new file mode 100644
index 000000000000..fc3530acfdd9
--- /dev/null
+++ b/platform/ios/app_ios.swift
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* app_ios.swift */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+import SwiftUI
+
+@main
+struct SwiftUIApp: App {
+ @UIApplicationDelegateAdaptor(GDTAppDelegateIOS.self) var appDelegate
+ @Environment(\.scenePhase) private var scenePhase
+
+ var body: some Scene {
+ GodotWindowScene()
+ }
+}
diff --git a/platform/ios/bridging_header_ios.h b/platform/ios/bridging_header_ios.h
new file mode 100644
index 000000000000..77b8028425ba
--- /dev/null
+++ b/platform/ios/bridging_header_ios.h
@@ -0,0 +1,33 @@
+/**************************************************************************/
+/* bridging_header_ios.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import "godot_app_delegate_ios.h"
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index fa5211f9ddbe..112ca3aa76bc 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -481,6 +481,14 @@ String EditorExportPlatformIOS::_process_config_file_line(const Ref
+@end
diff --git a/platform/tvos/display_layer_tvos.mm b/platform/tvos/display_layer_tvos.mm
new file mode 100644
index 000000000000..8e3757819a8a
--- /dev/null
+++ b/platform/tvos/display_layer_tvos.mm
@@ -0,0 +1,47 @@
+/**************************************************************************/
+/* display_layer_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "display_layer_tvos.h"
+
+@implementation GDTMetalLayer
+
+- (void)initializeDisplayLayer {
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)startRenderDisplayLayer {
+}
+
+- (void)stopRenderDisplayLayer {
+}
+
+@end
diff --git a/platform/tvos/display_server_tvos.h b/platform/tvos/display_server_tvos.h
new file mode 100644
index 000000000000..95245a175b8c
--- /dev/null
+++ b/platform/tvos/display_server_tvos.h
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* display_server_tvos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/display_server_apple_embedded.h"
+
+@class UIScreen;
+
+class DisplayServerTVOS final : public DisplayServerAppleEmbedded {
+ GDSOFTCLASS(DisplayServerTVOS, DisplayServerAppleEmbedded);
+
+ _THREAD_SAFE_CLASS_
+
+ DisplayServerTVOS(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error);
+ ~DisplayServerTVOS();
+
+ UIScreen *_get_ui_screen(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const;
+
+public:
+ static DisplayServerTVOS *get_singleton();
+
+ static void register_tvos_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error);
+
+ virtual String get_name() const override;
+
+ virtual int screen_get_dpi(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = DisplayServerEnums::SCREEN_OF_MAIN_WINDOW) const override;
+
+protected:
+ virtual bool _screen_hdr_is_supported() const override;
+ virtual float _screen_potential_edr_headroom() const override;
+ virtual float _screen_current_edr_headroom() const override;
+};
diff --git a/platform/tvos/display_server_tvos.mm b/platform/tvos/display_server_tvos.mm
new file mode 100644
index 000000000000..0bd8aaf3c15e
--- /dev/null
+++ b/platform/tvos/display_server_tvos.mm
@@ -0,0 +1,120 @@
+/**************************************************************************/
+/* display_server_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "display_server_tvos.h"
+
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
+#import "drivers/apple_embedded/godot_view_apple_embedded.h"
+#import "drivers/apple_embedded/godot_view_controller.h"
+
+#import
+
+DisplayServerTVOS *DisplayServerTVOS::get_singleton() {
+ return (DisplayServerTVOS *)DisplayServerAppleEmbedded::get_singleton();
+}
+
+DisplayServerTVOS::DisplayServerTVOS(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error) :
+ DisplayServerAppleEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error) {
+}
+
+DisplayServerTVOS::~DisplayServerTVOS() {
+}
+
+DisplayServer *DisplayServerTVOS::create_func(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error) {
+ return memnew(DisplayServerTVOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error));
+}
+
+void DisplayServerTVOS::register_tvos_driver() {
+ register_create_function("tvOS", create_func, get_rendering_drivers_func);
+}
+
+String DisplayServerTVOS::get_name() const {
+ return "tvOS";
+}
+
+int DisplayServerTVOS::screen_get_dpi(int p_screen) const {
+ p_screen = _get_screen_index(p_screen);
+ int screen_count = get_screen_count();
+ ERR_FAIL_INDEX_V(p_screen, screen_count, 72);
+
+ // No way to reliably determine DPI.
+ return 72;
+}
+
+UIScreen *DisplayServerTVOS::_get_ui_screen(int p_screen) const {
+ p_screen = _get_screen_index(p_screen);
+ int screen_count = get_screen_count();
+ ERR_FAIL_INDEX_V(p_screen, screen_count, nil);
+
+ return GDTAppDelegateService.viewController.godotView.window.windowScene.screen;
+}
+
+float DisplayServerTVOS::screen_get_refresh_rate(int p_screen) const {
+ UIScreen *screen = _get_ui_screen(p_screen);
+ ERR_FAIL_NULL_V(screen, SCREEN_REFRESH_RATE_FALLBACK);
+ float fps = screen.maximumFramesPerSecond;
+ if ([NSProcessInfo processInfo].lowPowerModeEnabled) {
+ fps = 60;
+ }
+ return fps;
+}
+
+float DisplayServerTVOS::screen_get_scale(int p_screen) const {
+ UIScreen *screen = _get_ui_screen(p_screen);
+ ERR_FAIL_NULL_V(screen, 1.0f);
+
+ return screen.scale;
+}
+
+// TODO: tvOS virtual keyboard support.
+// UITextView.editable is unavailable on tvOS and UIKeyInput on custom views
+// doesn't trigger the keyboard (rdar://27389949). UITextField works but needs
+// further integration with the Godot input system. For now, keyboard input
+// is not supported on tvOS.
+
+// MARK: HDR
+
+bool DisplayServerTVOS::_screen_hdr_is_supported() const {
+ UIScreen *screen = _get_ui_screen();
+ ERR_FAIL_NULL_V(screen, false);
+ return screen.potentialEDRHeadroom > 1.0;
+}
+
+float DisplayServerTVOS::_screen_potential_edr_headroom() const {
+ UIScreen *screen = _get_ui_screen();
+ ERR_FAIL_NULL_V(screen, false);
+ return screen.potentialEDRHeadroom;
+}
+
+float DisplayServerTVOS::_screen_current_edr_headroom() const {
+ UIScreen *screen = _get_ui_screen();
+ ERR_FAIL_NULL_V(screen, false);
+ return screen.currentEDRHeadroom;
+}
diff --git a/platform/tvos/doc_classes/EditorExportPlatformTVOS.xml b/platform/tvos/doc_classes/EditorExportPlatformTVOS.xml
new file mode 100644
index 000000000000..c1ff1c8c3f44
--- /dev/null
+++ b/platform/tvos/doc_classes/EditorExportPlatformTVOS.xml
@@ -0,0 +1,10 @@
+
+
+
+ Exporter for tvOS.
+
+
+
+
+
+
diff --git a/platform/tvos/export/export.cpp b/platform/tvos/export/export.cpp
new file mode 100644
index 000000000000..1a0a62cfb27e
--- /dev/null
+++ b/platform/tvos/export/export.cpp
@@ -0,0 +1,57 @@
+/**************************************************************************/
+/* export.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "export.h"
+
+#include "export_plugin.h"
+
+#include "core/object/class_db.h"
+#include "editor/export/editor_export.h"
+
+#ifdef MACOS_ENABLED
+#include "editor/settings/editor_settings.h"
+#endif
+
+void register_tvos_exporter_types() {
+ GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformTVOS);
+}
+
+void register_tvos_exporter() {
+ // TODO: Move to editor_settings.cpp
+#ifdef MACOS_ENABLED
+ EDITOR_DEF("export/tvos/tvos_deploy", "");
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/tvos/tvos_deploy", PROPERTY_HINT_GLOBAL_FILE, "*"));
+#endif
+
+ Ref platform;
+ platform.instantiate();
+
+ EditorExport::get_singleton()->add_export_platform(platform);
+}
diff --git a/platform/tvos/export/export.h b/platform/tvos/export/export.h
new file mode 100644
index 000000000000..2ac4eea22135
--- /dev/null
+++ b/platform/tvos/export/export.h
@@ -0,0 +1,34 @@
+/**************************************************************************/
+/* export.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+void register_tvos_exporter_types();
+void register_tvos_exporter();
diff --git a/platform/tvos/export/export_plugin.cpp b/platform/tvos/export/export_plugin.cpp
new file mode 100644
index 000000000000..8654e64d65f5
--- /dev/null
+++ b/platform/tvos/export/export_plugin.cpp
@@ -0,0 +1,298 @@
+/**************************************************************************/
+/* export_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "export_plugin.h"
+
+#include "logo_svg.gen.h"
+#include "run_icon_svg.gen.h"
+
+#include "editor/editor_node.h"
+#include "main/splash.gen.h"
+
+Vector EditorExportPlatformTVOS::device_types({ "appleTV" });
+
+void EditorExportPlatformTVOS::initialize() {
+ if (EditorNode::get_singleton()) {
+ EditorExportPlatformAppleEmbedded::_initialize(_tvos_logo_svg, _tvos_run_icon_svg);
+#ifdef MACOS_ENABLED
+ _start_remote_device_poller_thread();
+#endif
+ }
+}
+
+EditorExportPlatformTVOS::~EditorExportPlatformTVOS() {
+#ifdef MACOS_ENABLED
+ _stop_remote_device_poller_thread();
+#endif
+}
+
+void EditorExportPlatformTVOS::get_export_options(List *r_options) const {
+ EditorExportPlatformAppleEmbedded::get_export_options(r_options);
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_tvos_version"), get_minimum_deployment_target()));
+}
+
+bool EditorExportPlatformTVOS::has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool valid = EditorExportPlatformAppleEmbedded::has_valid_export_configuration(p_preset, r_error, r_missing_templates, p_debug);
+
+ String err;
+ String rendering_method = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile");
+ String rendering_driver = get_project_setting(p_preset, "rendering/rendering_device/driver." + get_platform_name());
+ if ((rendering_method == "forward_plus" || rendering_method == "mobile") && rendering_driver == "metal") {
+ float version = p_preset->get("application/min_tvos_version").operator String().to_float();
+ if (version < 14.0) {
+ err += TTR("Metal renderer require tvOS 14+.") + "\n";
+ }
+ }
+
+ if (!err.is_empty()) {
+ if (!r_error.is_empty()) {
+ r_error += err;
+ } else {
+ r_error = err;
+ }
+ }
+
+ return valid;
+}
+
+HashMap EditorExportPlatformTVOS::get_custom_project_settings(const Ref &p_preset) const {
+ return HashMap();
+}
+
+Error EditorExportPlatformTVOS::_export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) {
+ return OK;
+}
+
+Vector EditorExportPlatformTVOS::get_icon_infos() const {
+ return {
+ // tvOS App Icons (flat, v1)
+ { PNAME("icons/tvos_small_app_icon"), "tv", "AppIcon-400x240", "400", "1x", "400x240", true },
+ { PNAME("icons/tvos_large_app_icon"), "tv", "AppIcon-1280x768", "1280", "1x", "1280x768", true },
+ { PNAME("icons/tvos_top_shelf"), "tv", "TopShelf-1920x720", "1920", "1x", "1920x720", false },
+ { PNAME("icons/tvos_top_shelf_wide"), "tv", "TopShelfWide-2320x720", "2320", "1x", "2320x720", false },
+ };
+}
+
+Error EditorExportPlatformTVOS::_export_icons(const Ref &p_preset, const String &p_iconset_dir) {
+ String json_description = "{\"images\":[";
+ String sizes;
+
+ Ref da = DirAccess::open(p_iconset_dir);
+ if (da.is_null()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat(TTR("Could not open a directory at path \"%s\"."), p_iconset_dir));
+ return ERR_CANT_OPEN;
+ }
+
+ Color boot_bg_color = get_project_setting(p_preset, "application/boot_splash/bg_color");
+
+ struct TVOSIconSize {
+ int width;
+ int height;
+ };
+
+ // tvOS icons are not square, so we need width and height for each.
+ const TVOSIconSize tvos_icon_sizes[] = {
+ { 400, 240 },
+ { 1280, 768 },
+ { 1920, 720 },
+ { 2320, 720 },
+ };
+
+ Vector icon_infos = get_icon_infos();
+ bool first_icon = true;
+ for (int i = 0; i < icon_infos.size(); ++i) {
+ IconInfo info = icon_infos[i];
+ int target_width = tvos_icon_sizes[i].width;
+ int target_height = tvos_icon_sizes[i].height;
+ String key = info.preset_key;
+ String exp_name = String(info.export_name) + ".png";
+ String icon_path = p_preset->get(key);
+ bool resize_warning = true;
+ if (icon_path.is_empty()) {
+ // Try generic 1024x1024 icon.
+ key = "icons/icon_1024x1024";
+ icon_path = p_preset->get(key);
+ resize_warning = false;
+ }
+ if (icon_path.is_empty()) {
+ // Resize main app icon.
+ icon_path = get_project_setting(p_preset, "application/config/icon");
+ Error err = OK;
+ Ref img = _load_icon_or_splash_image(icon_path, &err);
+ if (err != OK || img.is_null() || img->is_empty()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ img->resize(target_width, target_height, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ Ref new_img = Image::create_empty(target_width, target_height, false, Image::FORMAT_RGBA8);
+ new_img->fill(boot_bg_color);
+ _blend_and_rotate(new_img, img, false);
+ err = new_img->save_png(p_iconset_dir + exp_name);
+ } else {
+ img->resize(target_width, target_height, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ err = img->save_png(p_iconset_dir + exp_name);
+ }
+ if (err) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
+ return err;
+ }
+ } else {
+ // Load custom icon and resize if required.
+ Error err = OK;
+ Ref img = _load_icon_or_splash_image(icon_path, &err);
+ if (err != OK || img.is_null() || img->is_empty()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ if (resize_warning) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
+ }
+ img->resize(target_width, target_height, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ Ref new_img = Image::create_empty(target_width, target_height, false, Image::FORMAT_RGBA8);
+ new_img->fill(boot_bg_color);
+ _blend_and_rotate(new_img, img, false);
+ err = new_img->save_png(p_iconset_dir + exp_name);
+ } else if (img->get_width() != target_width || img->get_height() != target_height) {
+ if (resize_warning) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(target_width, target_height)));
+ }
+ img->resize(target_width, target_height, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ err = img->save_png(p_iconset_dir + exp_name);
+ } else if (!icon_path.ends_with(".png")) {
+ err = img->save_png(p_iconset_dir + exp_name);
+ } else {
+ err = da->copy(icon_path, p_iconset_dir + exp_name);
+ }
+
+ if (err) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
+ return err;
+ }
+ }
+ sizes += String(info.actual_size_side) + "\n";
+ if (first_icon) {
+ first_icon = false;
+ } else {
+ json_description += ",";
+ }
+ json_description += String("{");
+ json_description += String("\"idiom\":") + "\"" + info.idiom + "\",";
+ json_description += String("\"platform\":\"" + get_platform_name() + "\",");
+ json_description += String("\"size\":") + "\"" + info.unscaled_size + "\",";
+ if (String(info.scale) != "1x") {
+ json_description += String("\"scale\":") + "\"" + info.scale + "\",";
+ }
+ json_description += String("\"filename\":") + "\"" + exp_name + "\"";
+ json_description += String("}");
+ }
+ json_description += "],\"info\":{\"author\":\"xcode\",\"version\":1}}";
+
+ Ref json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
+ if (json_file.is_null()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat(TTR("Could not write to a file at path \"%s\"."), p_iconset_dir + "Contents.json"));
+ return ERR_CANT_CREATE;
+ }
+
+ CharString json_utf8 = json_description.utf8();
+ json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length());
+
+ Ref sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE);
+ if (sizes_file.is_null()) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat(TTR("Could not write to a file at path \"%s\"."), p_iconset_dir + "sizes"));
+ return ERR_CANT_CREATE;
+ }
+
+ CharString sizes_utf8 = sizes.utf8();
+ sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length());
+
+ return OK;
+}
+
+String EditorExportPlatformTVOS::_process_config_file_line(const Ref &p_preset, const String &p_line, const AppleEmbeddedConfigData &p_config, bool p_debug, const CodeSigningDetails &p_code_signing) {
+ // Do tvOS specific processing first, and call super implementation if there are no matches
+
+ String strnew;
+
+ // Supported Destinations
+ if (p_line.contains("$targeted_device_family")) {
+ strnew += p_line.replace("$targeted_device_family", "3") + "\n";
+
+ // MoltenVK Framework not used on tvOS
+ } else if (p_line.contains("$moltenvk_buildfile")) {
+ strnew += p_line.replace("$moltenvk_buildfile", "") + "\n";
+ } else if (p_line.contains("$moltenvk_fileref")) {
+ strnew += p_line.replace("$moltenvk_fileref", "") + "\n";
+ } else if (p_line.contains("$moltenvk_buildphase")) {
+ strnew += p_line.replace("$moltenvk_buildphase", "") + "\n";
+ } else if (p_line.contains("$moltenvk_buildgrp")) {
+ strnew += p_line.replace("$moltenvk_buildgrp", "") + "\n";
+
+ // Launch Storyboard (not used on tvOS)
+ } else if (p_line.contains("$plist_launch_screen_name")) {
+ strnew += p_line.replace("$plist_launch_screen_name", "") + "\n";
+ } else if (p_line.contains("$pbx_launch_screen_file_reference")) {
+ strnew += p_line.replace("$pbx_launch_screen_file_reference", "") + "\n";
+ } else if (p_line.contains("$pbx_launch_screen_copy_files")) {
+ strnew += p_line.replace("$pbx_launch_screen_copy_files", "") + "\n";
+ } else if (p_line.contains("$pbx_launch_screen_build_phase")) {
+ strnew += p_line.replace("$pbx_launch_screen_build_phase", "") + "\n";
+ } else if (p_line.contains("$pbx_launch_screen_build_reference")) {
+ strnew += p_line.replace("$pbx_launch_screen_build_reference", "") + "\n";
+ } else if (p_line.contains("$launch_screen_image_mode")) {
+ strnew += p_line.replace("$launch_screen_image_mode", "") + "\n";
+ } else if (p_line.contains("$launch_screen_background_color")) {
+ strnew += p_line.replace("$launch_screen_background_color", "") + "\n";
+
+ // OS Deployment Target
+ } else if (p_line.contains("$os_deployment_target")) {
+ String min_version = p_preset->get("application/min_" + get_platform_name() + "_version");
+ String value = "TVOS_DEPLOYMENT_TARGET = " + min_version + ";";
+ strnew += p_line.replace("$os_deployment_target", value) + "\n";
+
+ // Valid Archs
+ } else if (p_line.contains("$valid_archs")) {
+ strnew += p_line.replace("$valid_archs", "arm64 x86_64") + "\n";
+
+ // Application Scene Manifest - Default Session Role
+ } else if (p_line.contains("$application_scene_manifest_default_session_role")) {
+ strnew += p_line.replace("$application_scene_manifest_default_session_role", "") + "\n";
+
+ // Application Scene Manifest - Immersive Configuration
+ } else if (p_line.contains("$application_scene_manifest_immersive_configuration")) {
+ strnew += p_line.replace("$application_scene_manifest_immersive_configuration", "") + "\n";
+
+ // Apple Embedded common
+ } else {
+ strnew += EditorExportPlatformAppleEmbedded::_process_config_file_line(p_preset, p_line, p_config, p_debug, p_code_signing);
+ }
+
+ return strnew;
+}
diff --git a/platform/tvos/export/export_plugin.h b/platform/tvos/export/export_plugin.h
new file mode 100644
index 000000000000..dc7390035618
--- /dev/null
+++ b/platform/tvos/export/export_plugin.h
@@ -0,0 +1,68 @@
+/**************************************************************************/
+/* export_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "editor/export/editor_export_platform_apple_embedded.h"
+
+class EditorExportPlatformTVOS : public EditorExportPlatformAppleEmbedded {
+ GDCLASS(EditorExportPlatformTVOS, EditorExportPlatformAppleEmbedded);
+
+ static Vector device_types;
+
+ virtual String get_platform_name() const override { return "tvos"; }
+ virtual String get_sdk_name() const override { return "appletvos"; }
+ virtual const Vector get_device_types() const override { return device_types; }
+
+ virtual String get_minimum_deployment_target() const override { return "26.0"; }
+
+ virtual Vector get_icon_infos() const override;
+
+ virtual void get_export_options(List *r_options) const override;
+ virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+
+ virtual Error _export_loading_screen_file(const Ref &p_preset, const String &p_dest_dir) override;
+ virtual Error _export_icons(const Ref &p_preset, const String &p_iconset_dir) override;
+ virtual HashMap get_custom_project_settings(const Ref &p_preset) const override;
+
+ virtual String _process_config_file_line(const Ref &p_preset, const String &p_line, const AppleEmbeddedConfigData &p_config, bool p_debug, const CodeSigningDetails &p_code_signing) override;
+
+public:
+ virtual String get_name() const override { return "tvOS"; }
+ virtual String get_os_name() const override { return "tvOS"; }
+
+ virtual void get_platform_features(List *r_features) const override {
+ EditorExportPlatformAppleEmbedded::get_platform_features(r_features);
+ r_features->push_back("tvos");
+ }
+
+ virtual void initialize() override;
+ ~EditorExportPlatformTVOS();
+};
diff --git a/platform/tvos/export/logo.svg b/platform/tvos/export/logo.svg
new file mode 100644
index 000000000000..193ddd1e3179
--- /dev/null
+++ b/platform/tvos/export/logo.svg
@@ -0,0 +1 @@
+
diff --git a/platform/tvos/export/run_icon.svg b/platform/tvos/export/run_icon.svg
new file mode 100644
index 000000000000..193ddd1e3179
--- /dev/null
+++ b/platform/tvos/export/run_icon.svg
@@ -0,0 +1 @@
+
diff --git a/platform/tvos/godot_app_delegate_tvos.h b/platform/tvos/godot_app_delegate_tvos.h
new file mode 100644
index 000000000000..29e85f3d4ddf
--- /dev/null
+++ b/platform/tvos/godot_app_delegate_tvos.h
@@ -0,0 +1,37 @@
+/**************************************************************************/
+/* godot_app_delegate_tvos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/godot_app_delegate_apple_embedded.h"
+
+@interface GDTAppDelegateTVOS : GDTAppDelegate
+
+@end
diff --git a/platform/tvos/godot_app_delegate_tvos.mm b/platform/tvos/godot_app_delegate_tvos.mm
new file mode 100644
index 000000000000..b76c3dfab314
--- /dev/null
+++ b/platform/tvos/godot_app_delegate_tvos.mm
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* godot_app_delegate_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_app_delegate_tvos.h"
+
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
+
+@implementation GDTAppDelegateTVOS
+
++ (void)load {
+ [self addService:[GDTAppDelegateService new]];
+}
+
+@end
diff --git a/platform/tvos/godot_view_tvos.h b/platform/tvos/godot_view_tvos.h
new file mode 100644
index 000000000000..ef41bb261e4b
--- /dev/null
+++ b/platform/tvos/godot_view_tvos.h
@@ -0,0 +1,37 @@
+/**************************************************************************/
+/* godot_view_tvos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/godot_view_apple_embedded.h"
+
+@interface GDTViewTVOS : GDTView
+
+@end
diff --git a/platform/tvos/godot_view_tvos.mm b/platform/tvos/godot_view_tvos.mm
new file mode 100644
index 000000000000..2be860d32684
--- /dev/null
+++ b/platform/tvos/godot_view_tvos.mm
@@ -0,0 +1,79 @@
+/**************************************************************************/
+/* godot_view_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_view_tvos.h"
+
+#import "display_layer_tvos.h"
+
+#include "core/error/error_macros.h"
+
+#define Key GC_Key_
+#import
+#undef Key
+
+@interface GDTViewTVOS ()
+
+GODOT_CLANG_WARNING_PUSH_AND_IGNORE("-Wobjc-property-synthesis")
+@property(strong, nonatomic) CALayer *renderingLayer;
+GODOT_CLANG_WARNING_POP
+
+@end
+
+@implementation GDTViewTVOS
+
+- (void)godot_commonInit {
+ [super godot_commonInit];
+}
+
+- (CALayer *)initializeRenderingForDriver:(NSString *)driverName {
+ if (self.renderingLayer) {
+ return self.renderingLayer;
+ }
+
+ CALayer *layer = [GDTMetalLayer layer];
+
+ layer.frame = self.bounds;
+ layer.contentsScale = self.contentScaleFactor;
+
+ [self.layer addSublayer:layer];
+ self.renderingLayer = layer;
+
+ [layer initializeDisplayLayer];
+
+ return self.renderingLayer;
+}
+
+@end
+
+GDTView *GDTViewCreate() {
+ GDTViewTVOS *view = [GDTViewTVOS new];
+ view.preferredFrameRate = 60;
+ return view;
+}
diff --git a/platform/tvos/main_tvos.mm b/platform/tvos/main_tvos.mm
new file mode 100644
index 000000000000..3fd7d177187c
--- /dev/null
+++ b/platform/tvos/main_tvos.mm
@@ -0,0 +1,75 @@
+/**************************************************************************/
+/* main_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "os_tvos.h"
+
+#include "core/profiling/profiling.h"
+#import "drivers/apple_embedded/godot_app_delegate_apple_embedded.h"
+#import "drivers/apple_embedded/main_utilities.h"
+#include "main/main.h"
+
+#import
+
+#include
+
+static OS_TVOS *os = nullptr;
+
+int apple_embedded_main(int argc, char **argv) {
+ change_to_launch_dir(argv);
+
+ os = new OS_TVOS();
+
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
+
+ char *fargv[64];
+ argc = process_args(argc, argv, fargv);
+
+ godot_init_profiler();
+
+ Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
+
+ if (err != OK) {
+ if (err == ERR_HELP) { // Returned by --help and --version, so success.
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+ }
+
+ os->initialize_modules();
+
+ return os->get_exit_code();
+}
+
+void apple_embedded_finish() {
+ Main::cleanup();
+ godot_cleanup_profiler();
+ delete os;
+}
diff --git a/platform/tvos/os_tvos.h b/platform/tvos/os_tvos.h
new file mode 100644
index 000000000000..f821c29173ce
--- /dev/null
+++ b/platform/tvos/os_tvos.h
@@ -0,0 +1,47 @@
+/**************************************************************************/
+/* os_tvos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef TVOS_ENABLED
+
+#import "drivers/apple_embedded/os_apple_embedded.h"
+
+class OS_TVOS : public OS_AppleEmbedded {
+public:
+ static OS_TVOS *get_singleton();
+
+ OS_TVOS();
+ ~OS_TVOS();
+
+ virtual String get_name() const override;
+};
+
+#endif // TVOS_ENABLED
diff --git a/platform/tvos/os_tvos.mm b/platform/tvos/os_tvos.mm
new file mode 100644
index 000000000000..09b27c3ce134
--- /dev/null
+++ b/platform/tvos/os_tvos.mm
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* os_tvos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "os_tvos.h"
+
+#import "display_server_tvos.h"
+
+#ifdef TVOS_ENABLED
+
+OS_TVOS *OS_TVOS::get_singleton() {
+ return (OS_TVOS *)OS_AppleEmbedded::get_singleton();
+}
+
+OS_TVOS::OS_TVOS() :
+ OS_AppleEmbedded() {
+ DisplayServerTVOS::register_tvos_driver();
+}
+
+OS_TVOS::~OS_TVOS() {}
+
+String OS_TVOS::get_name() const {
+ return "tvOS";
+}
+
+#endif // TVOS_ENABLED
diff --git a/platform/tvos/platform_config.h b/platform/tvos/platform_config.h
new file mode 100644
index 000000000000..52dac8b496ee
--- /dev/null
+++ b/platform/tvos/platform_config.h
@@ -0,0 +1,33 @@
+/**************************************************************************/
+/* platform_config.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import "drivers/apple_embedded/platform_config.h"
diff --git a/platform/tvos/platform_gl.h b/platform/tvos/platform_gl.h
new file mode 100644
index 000000000000..a3f97cf64f00
--- /dev/null
+++ b/platform/tvos/platform_gl.h
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* platform_gl.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#ifdef GLES3_ENABLED
+#ifndef GLES_API_ENABLED
+#define GLES_API_ENABLED // Allow using GLES.
+#endif
+
+#include // IWYU pragma: export.
+#endif
diff --git a/platform/tvos/platform_thread.h b/platform/tvos/platform_thread.h
new file mode 100644
index 000000000000..42827e53fc8c
--- /dev/null
+++ b/platform/tvos/platform_thread.h
@@ -0,0 +1,33 @@
+/**************************************************************************/
+/* platform_thread.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple/thread_apple.h"
diff --git a/platform/tvos/platform_tvos_builders.py b/platform/tvos/platform_tvos_builders.py
new file mode 100644
index 000000000000..39e5cca67c99
--- /dev/null
+++ b/platform/tvos/platform_tvos_builders.py
@@ -0,0 +1,7 @@
+"""Functions used to generate source files during build time"""
+
+from platform_methods import generate_bundle_apple_embedded
+
+
+def generate_bundle(target, source, env):
+ generate_bundle_apple_embedded("tvos", "tvos-arm64", "tvos-arm64_x86_64-simulator", False, target, source, env)
diff --git a/platform/tvos/tvos.h b/platform/tvos/tvos.h
new file mode 100644
index 000000000000..ffda22824ea1
--- /dev/null
+++ b/platform/tvos/tvos.h
@@ -0,0 +1,37 @@
+/**************************************************************************/
+/* tvos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/apple_embedded.h"
+
+class tvOS : public AppleEmbedded {
+ GDCLASS(tvOS, AppleEmbedded);
+};
diff --git a/platform/visionos/SCsub b/platform/visionos/SCsub
index 8f64654d8bd8..4748ccbc784c 100644
--- a/platform/visionos/SCsub
+++ b/platform/visionos/SCsub
@@ -2,25 +2,41 @@
from misc.utility.scons_hints import *
from platform_visionos_builders import generate_bundle
+from SCons.Script import Glob
-from platform_methods import combine_libs_apple_embedded
+from platform_methods import combine_libs_apple_embedded, setup_swift_builder
Import("env")
-visionos_lib = [
- "display_layer_visionos.mm",
- "display_server_visionos.mm",
- "godot_view_visionos.mm",
- "main_visionos.mm",
- "os_visionos.mm",
-]
+swift_files = Glob("*.swift")
+swift_file_names = list(map(lambda f: f.name, swift_files))
+
+mm_files = Glob("*.mm")
+mm_file_names = list(map(lambda f: f.name, mm_files))
+
+visionos_lib = swift_file_names + mm_file_names
env_visionos = env.Clone()
-visionos_lib = env_visionos.add_library("visionos", visionos_lib)
+
+# Configure Swift builder
+additional_swift_file_names = ["../../drivers/apple_embedded/godot_swiftui_view_controller.swift"]
+apple_platform = env["APPLE_PLATFORM"]
+sdk_path = env["APPLE_SDK_PATH"]
+current_path = Dir(".").abspath
+bridging_header_filename = "bridging_header_visionos.h"
+setup_swift_builder(
+ env_visionos,
+ apple_platform,
+ sdk_path,
+ current_path,
+ bridging_header_filename,
+ swift_file_names + additional_swift_file_names,
+)
# Enable module support
env_visionos.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
+visionos_lib = env_visionos.add_library("visionos", visionos_lib)
if "LIBS_EXTERNAL" in env_visionos:
env_visionos.Depends(visionos_lib, env_visionos["LIBS_EXTERNAL"])
diff --git a/platform/visionos/app_visionos.swift b/platform/visionos/app_visionos.swift
new file mode 100644
index 000000000000..85d62141569c
--- /dev/null
+++ b/platform/visionos/app_visionos.swift
@@ -0,0 +1,139 @@
+/**************************************************************************/
+/* app_visionos.swift */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+import SwiftUI
+@preconcurrency import CompositorServices
+
+// MARK: Renderer
+
+final class RendererTaskExecutor: TaskExecutor {
+ private let queue = DispatchQueue(label: "RenderThreadQueue", qos: .userInteractive)
+ func enqueue(_ job: UnownedJob) {
+ queue.async {
+ job.runSynchronously(on: self.asUnownedSerialExecutor())
+ }
+ }
+ nonisolated func asUnownedSerialExecutor() -> UnownedTaskExecutor {
+ return UnownedTaskExecutor(ordinary: self)
+ }
+ static let shared: RendererTaskExecutor = RendererTaskExecutor()
+}
+
+// MARK: Compositor Services Scene
+
+struct ContentStageConfiguration: CompositorLayerConfiguration {
+ func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) {
+
+ GDTAppDelegateServiceVisionOS.layerRendererCapabilities = capabilities as __CP_OBJECT_cp_layer_renderer_capabilities
+
+ configuration.depthFormat = .depth32Float_stencil8
+ configuration.colorFormat = .rgba16Float
+
+ let foveationEnabled = capabilities.supportsFoveation
+ configuration.isFoveationEnabled = foveationEnabled
+
+ let options: LayerRenderer.Capabilities.SupportedLayoutsOptions = foveationEnabled ? [.foveationEnabled] : []
+ let supportedLayouts = capabilities.supportedLayouts(options: options)
+ if (!supportedLayouts.contains(.layered)) {
+ fatalError("Only the .layered layout is supported by Godot's visionOS XR module.")
+ }
+ configuration.layout = .layered
+ }
+}
+
+extension GDTCompositorServicesRenderer: @unchecked Sendable {}
+
+struct CompositorServicesImmersiveSpace: Scene {
+
+ fileprivate static var initialImmersionStyle: ImmersionStyle {
+ guard let sceneManifest = Bundle.main.infoDictionary?["UIApplicationSceneManifest"] as? [String: Any],
+ let sceneConfigurations = sceneManifest["UISceneConfigurations"] as? [String: Any],
+ let cpSceneConfiguration = sceneConfigurations["UISceneSessionRoleImmersiveSpaceApplication"] as? [[String: Any]],
+ let immersionStyleString = cpSceneConfiguration.first?["UISceneInitialImmersionStyle"] as? String else {
+ return .full
+ }
+ switch immersionStyleString {
+ case "UIImmersionStyleFull": return .full
+ case "UIImmersionStyleMixed": return .mixed
+ default: return .full
+ }
+ }
+
+ @State var renderer: GDTCompositorServicesRenderer!
+ @State var didSetUpRenderer: Bool = false
+
+ var body: some Scene {
+ ImmersiveSpace(id: "ImmersiveSpace") {
+ CompositorLayer(configuration: ContentStageConfiguration()) { @MainActor layerRenderer in
+ GDTAppDelegateServiceVisionOS.layerRenderer = layerRenderer
+ renderer = GDTCompositorServicesRenderer(layerRenderer: layerRenderer,
+ capabilities: GDTAppDelegateServiceVisionOS.layerRendererCapabilities)
+ if !didSetUpRenderer {
+ renderer.setUp()
+ didSetUpRenderer = true
+ } else {
+ renderer.updateXRInterface()
+ }
+ Task(executorPreference: RendererTaskExecutor.shared) {
+ await renderer.startRenderLoop()
+ }
+ }
+ .onWorldRecenter {
+ renderer.worldRecentered()
+ }
+ }
+ .immersionStyle(selection: .constant(Self.initialImmersionStyle), in: .mixed, .full)
+ }
+}
+
+// MARK: App
+
+@main
+struct SwiftUIApp: App {
+ @UIApplicationDelegateAdaptor(GDTAppDelegateVisionOS.self) var appDelegate
+
+ private var useCompositorServices: Bool = {
+ guard let sceneManifest = Bundle.main.infoDictionary?["UIApplicationSceneManifest"] as? [String: Any],
+ let defaultSessionRole = sceneManifest["UIApplicationPreferredDefaultSceneSessionRole"] as? String else {
+ return false
+ }
+ return defaultSessionRole == "CPSceneSessionRoleImmersiveSpaceApplication"
+ }()
+
+ init() {
+ print("visionOS app init (useCompositorServices: \(useCompositorServices))")
+ GDTAppDelegateServiceVisionOS.renderMode = useCompositorServices ? .compositorServices : .windowed
+ }
+
+ var body: some Scene {
+ GodotWindowScene()
+ CompositorServicesImmersiveSpace()
+ }
+}
diff --git a/platform/visionos/bridging_header_visionos.h b/platform/visionos/bridging_header_visionos.h
new file mode 100644
index 000000000000..82b09ac551d5
--- /dev/null
+++ b/platform/visionos/bridging_header_visionos.h
@@ -0,0 +1,35 @@
+/**************************************************************************/
+/* bridging_header_visionos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import "godot_app_delegate_service_visionos.h"
+#import "godot_app_delegate_visionos.h"
+#import "godot_compositor_services_renderer.h"
diff --git a/platform/visionos/display_server_visionos.mm b/platform/visionos/display_server_visionos.mm
index 357818bac5d7..a27da03afa27 100644
--- a/platform/visionos/display_server_visionos.mm
+++ b/platform/visionos/display_server_visionos.mm
@@ -30,12 +30,22 @@
#import "display_server_visionos.h"
+#import "core/os/os.h"
+
+#import "platform/visionos/godot_app_delegate_service_visionos.h"
+
DisplayServerVisionOS *DisplayServerVisionOS::get_singleton() {
return (DisplayServerVisionOS *)DisplayServerAppleEmbedded::get_singleton();
}
DisplayServerVisionOS::DisplayServerVisionOS(const String &p_rendering_driver, DisplayServerEnums::WindowMode p_mode, DisplayServerEnums::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, DisplayServerEnums::Context p_context, int64_t p_parent_window, Error &r_error) :
DisplayServerAppleEmbedded(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error) {
+ String rendering_method = OS::get_singleton()->get_current_rendering_method();
+ GDTRenderMode app_delegate_render_mode = GDTAppDelegateServiceVisionOS.renderMode;
+ if (app_delegate_render_mode == GDTRenderModeCompositorServices && rendering_method == "forward_plus") {
+ WARN_PRINT_ONCE("visionOS in immersive mode doesn't support the Forward+ renderer, switching to the Mobile renderer.");
+ OS::get_singleton()->set_current_rendering_method("mobile", OS::RENDERING_SOURCE_FALLBACK);
+ }
}
DisplayServerVisionOS::~DisplayServerVisionOS() {
diff --git a/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml b/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml
index 97e70f629dc9..4737e38b7bff 100644
--- a/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml
+++ b/platform/visionos/doc_classes/EditorExportPlatformVisionOS.xml
@@ -17,6 +17,9 @@
<string>value</string>
[/codeblock]
+
+ The application role on the visionOS platform. It can be [code skip-lint]Window[/code] for running a 3D game on a 2D window, or [code skip-lint]Immersive[/code] for an XR experience.
+
Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url].
@@ -44,14 +47,17 @@
Interpolation method used to resize application icon.
+
+ The immersion style, only applicable if you have chosen the [code]Immersive[/code] app role. It can be [code]Full[/code] for a VR experience, or [code]Mixed[/code] for a mixed reality experience where the rendered content is display along with the real environment.
+
- Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
+ Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_DEBUG[/code].
- Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
+ Name of the provisioning profile. Sets Xcode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url].
Can be overridden with the environment variable [code]GODOT_APPLE_PLATFORM_PROFILE_SPECIFIER_RELEASE[/code].
diff --git a/platform/visionos/export/export_plugin.cpp b/platform/visionos/export/export_plugin.cpp
index cdf90283ed06..ced337a2e12d 100644
--- a/platform/visionos/export/export_plugin.cpp
+++ b/platform/visionos/export/export_plugin.cpp
@@ -56,6 +56,9 @@ void EditorExportPlatformVisionOS::get_export_options(List *r_opti
EditorExportPlatformAppleEmbedded::get_export_options(r_options);
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_visionos_version"), get_minimum_deployment_target()));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/app_role", PROPERTY_HINT_ENUM, "Window,Immersive"), 0));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/immersion_style", PROPERTY_HINT_ENUM, "Full,Mixed"), 1));
}
Vector EditorExportPlatformVisionOS::get_icon_infos() const {
@@ -103,6 +106,93 @@ String EditorExportPlatformVisionOS::_process_config_file_line(const Refget("application/app_role");
+ if (app_role_enum == 0) {
+ // Windowed mode, not needed
+ strnew += p_line.replace("$application_scene_manifest_default_session_role", "") + "\n";
+ return strnew;
+ }
+
+ String value =
+ "UIApplicationPreferredDefaultSceneSessionRole\n"
+ "CPSceneSessionRoleImmersiveSpaceApplication";
+
+ strnew += p_line.replace("$application_scene_manifest_default_session_role", value) + "\n";
+
+ // Application Scene Manifest - Immersive Configuration
+ } else if (p_line.contains("$application_scene_manifest_immersive_configuration")) {
+ int app_role_enum = (int)p_preset->get("application/app_role");
+ if (app_role_enum == 0) {
+ // Windowed mode, not needed
+ strnew += p_line.replace("$application_scene_manifest_immersive_configuration", "") + "\n";
+ return strnew;
+ }
+
+ String initial_immersion_style;
+ switch ((int)p_preset->get("application/immersion_style")) {
+ case 0: // Full
+ initial_immersion_style = "UIImmersionStyleFull";
+ break;
+ case 1: // Mixed
+ initial_immersion_style = "UIImmersionStyleMixed";
+ break;
+ }
+
+ String value =
+ "UISceneSessionRoleImmersiveSpaceApplication\n"
+ "\n"
+ " \n"
+ " UISceneInitialImmersionStyle\n"
+ " " +
+ initial_immersion_style + "\n"
+ " \n"
+ "";
+
+ strnew += p_line.replace("$application_scene_manifest_immersive_configuration", value) + "\n";
+
+ // Application Scene Manifest
+ } else if (p_line.contains("$application_scene_manifest")) {
+ int app_role_enum = (int)p_preset->get("application/app_role");
+ if (app_role_enum == 0) {
+ // Windowed mode, no Application Scene Manifest needed
+ strnew += p_line.replace("$application_scene_manifest", "") + "\n";
+ return strnew;
+ }
+
+ String initial_immersion_style;
+ switch ((int)p_preset->get("application/immersion_style")) {
+ case 0: // Full
+ initial_immersion_style = "UIImmersionStyleFull";
+ break;
+ case 1: // Mixed
+ initial_immersion_style = "UIImmersionStyleMixed";
+ break;
+ }
+
+ String value =
+ "UIApplicationSceneManifest\n"
+ "\n"
+ " UIApplicationPreferredDefaultSceneSessionRole\n"
+ " CPSceneSessionRoleImmersiveSpaceApplication\n"
+ " UIApplicationSupportsMultipleScenes\n"
+ " \n"
+ " UISceneConfigurations\n"
+ " \n"
+ " UISceneSessionRoleImmersiveSpaceApplication\n"
+ " \n"
+ " \n"
+ " UISceneInitialImmersionStyle\n"
+ " " +
+ initial_immersion_style + "\n"
+ " \n"
+ " \n"
+ " \n"
+ "";
+
+ strnew += p_line.replace("$application_scene_manifest", value) + "\n";
+
// Apple Embedded common
} else {
strnew += EditorExportPlatformAppleEmbedded::_process_config_file_line(p_preset, p_line, p_config, p_debug, p_code_signing);
diff --git a/platform/visionos/godot_app_delegate_service_visionos.h b/platform/visionos/godot_app_delegate_service_visionos.h
new file mode 100644
index 000000000000..7604cdaa9ac9
--- /dev/null
+++ b/platform/visionos/godot_app_delegate_service_visionos.h
@@ -0,0 +1,50 @@
+/**************************************************************************/
+/* godot_app_delegate_visionos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import "render_mode_visionos.h"
+
+#import "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h"
+
+#import
+
+typedef NS_ENUM(NSInteger, GDTRenderMode) {
+ GDTRenderModeWindowed = RenderModeVisionOS::WINDOWED,
+ GDTRenderModeCompositorServices = RenderModeVisionOS::COMPOSITOR_SERVICES
+};
+
+@interface GDTAppDelegateServiceVisionOS : GDTAppDelegateService
+
+@property(assign, class, nonatomic) GDTRenderMode renderMode;
+@property(weak, class, nonatomic, nullable) cp_layer_renderer_t layerRenderer;
+@property(strong, class, nonatomic, nullable) cp_layer_renderer_capabilities_t layerRendererCapabilities;
+
+@end
diff --git a/platform/visionos/godot_app_delegate_service_visionos.mm b/platform/visionos/godot_app_delegate_service_visionos.mm
new file mode 100644
index 000000000000..966769794db0
--- /dev/null
+++ b/platform/visionos/godot_app_delegate_service_visionos.mm
@@ -0,0 +1,79 @@
+/**************************************************************************/
+/* godot_app_delegate_visionos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_app_delegate_service_visionos.h"
+
+static GDTRenderMode _renderMode = GDTRenderModeWindowed;
+static __weak cp_layer_renderer_t _layerRenderer = nil;
+static __strong cp_layer_renderer_capabilities_t _layerRendererCapabilities = nil;
+
+@implementation GDTAppDelegateServiceVisionOS
+
++ (GDTRenderMode)renderMode {
+ return _renderMode;
+}
+
++ (void)setRenderMode:(GDTRenderMode)renderMode {
+ _renderMode = renderMode;
+}
+
++ (cp_layer_renderer_t)layerRenderer {
+ if (_renderMode != GDTRenderModeCompositorServices) {
+ NSLog(@"GDTAppDelegate error, layerRenderer only supported in Compositor Services mode");
+ return nil;
+ }
+ return _layerRenderer;
+}
+
++ (void)setLayerRenderer:(cp_layer_renderer_t)layerRenderer {
+ if (_renderMode != GDTRenderModeCompositorServices) {
+ NSLog(@"GDTAppDelegate error, layerRenderer only supported in Compositor Services mode");
+ return;
+ }
+ _layerRenderer = layerRenderer;
+}
+
++ (cp_layer_renderer_capabilities_t)layerRendererCapabilities {
+ if (_renderMode != GDTRenderModeCompositorServices) {
+ NSLog(@"GDTAppDelegate error, layerRenderer only supported in Compositor Services mode");
+ return nil;
+ }
+ return _layerRendererCapabilities;
+}
+
++ (void)setLayerRendererCapabilities:(cp_layer_renderer_capabilities_t)layerRendererCapabilities {
+ if (_renderMode != GDTRenderModeCompositorServices) {
+ NSLog(@"GDTAppDelegate error, layerRenderer only supported in Compositor Services mode");
+ return;
+ }
+ _layerRendererCapabilities = layerRendererCapabilities;
+}
+
+@end
diff --git a/platform/visionos/godot_app_delegate_visionos.h b/platform/visionos/godot_app_delegate_visionos.h
new file mode 100644
index 000000000000..9114f9fea356
--- /dev/null
+++ b/platform/visionos/godot_app_delegate_visionos.h
@@ -0,0 +1,37 @@
+/**************************************************************************/
+/* godot_app_delegate_visionos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include "drivers/apple_embedded/godot_app_delegate_apple_embedded.h"
+
+@interface GDTAppDelegateVisionOS : GDTAppDelegate
+
+@end
diff --git a/platform/visionos/godot_app_delegate_visionos.mm b/platform/visionos/godot_app_delegate_visionos.mm
new file mode 100644
index 000000000000..7139f552fab4
--- /dev/null
+++ b/platform/visionos/godot_app_delegate_visionos.mm
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* godot_app_delegate_visionos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_app_delegate_visionos.h"
+
+#include "godot_app_delegate_service_visionos.h"
+
+@implementation GDTAppDelegateVisionOS
+
++ (void)load {
+ [self addService:[GDTAppDelegateServiceVisionOS new]];
+}
+
+@end
diff --git a/platform/visionos/godot_compositor_services_renderer.h b/platform/visionos/godot_compositor_services_renderer.h
new file mode 100644
index 000000000000..47504dfe7932
--- /dev/null
+++ b/platform/visionos/godot_compositor_services_renderer.h
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* godot_compositor_services_renderer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#import "drivers/apple_embedded/godot_renderer.h"
+
+#import
+
+@interface GDTCompositorServicesRenderer : GDTRenderer
+
+- (instancetype)initWithLayerRenderer:(cp_layer_renderer_t)layer_renderer
+ capabilities:(cp_layer_renderer_capabilities_t)capabilities;
+
+- (void)updateXRInterface;
+
+- (void)startRenderLoop;
+- (void)renderFrame;
+- (void)worldRecentered;
+
+@end
diff --git a/platform/visionos/godot_compositor_services_renderer.mm b/platform/visionos/godot_compositor_services_renderer.mm
new file mode 100644
index 000000000000..83521ae297ce
--- /dev/null
+++ b/platform/visionos/godot_compositor_services_renderer.mm
@@ -0,0 +1,116 @@
+/**************************************************************************/
+/* godot_compositor_services_renderer.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#import "godot_compositor_services_renderer.h"
+
+#import "drivers/apple_embedded/os_apple_embedded.h"
+
+#include "modules/visionos_xr/visionos_xr_interface.h"
+
+#import
+
+extern void apple_embedded_finish();
+
+@implementation GDTCompositorServicesRenderer {
+ cp_layer_renderer_t _layer_renderer;
+ cp_layer_renderer_capabilities_t _layer_renderer_capabilities;
+}
+
+- (instancetype)initWithLayerRenderer:(cp_layer_renderer_t)layer_renderer
+ capabilities:(cp_layer_renderer_capabilities_t)capabilities {
+ self = [super init];
+ if (self) {
+ _layer_renderer = layer_renderer;
+ _layer_renderer_capabilities = capabilities;
+ }
+ return self;
+}
+
+- (void)updateXRInterface {
+ Ref visionos_xr_interface = VisionOSXRInterface::find_interface();
+ if (visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->update_layer_renderer(_layer_renderer, _layer_renderer_capabilities);
+ }
+}
+
+- (void)startRenderLoop {
+ Ref visionos_xr_interface = VisionOSXRInterface::find_interface();
+ cp_layer_renderer_state previous_state = cp_layer_renderer_state_running;
+ if (visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->emit_signal_enum(VisionOSXRInterface::VISIONOS_XR_SIGNAL_SESSION_STARTED);
+ }
+ while (true) {
+ cp_layer_renderer_state state = cp_layer_renderer_get_state(_layer_renderer);
+ if (state == cp_layer_renderer_state_invalidated) {
+ if (visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->emit_signal_enum(VisionOSXRInterface::VISIONOS_XR_SIGNAL_SESSION_INVALIDATED);
+ }
+ // Exit render loop and wait for a new layer renderer
+ return;
+ } else if (state == cp_layer_renderer_state_paused) {
+ if (previous_state == cp_layer_renderer_state_running && visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->emit_signal_enum(VisionOSXRInterface::VISIONOS_XR_SIGNAL_SESSION_PAUSED);
+ }
+ previous_state = state;
+ cp_layer_renderer_wait_until_running(_layer_renderer);
+ continue;
+ } else {
+ @autoreleasepool {
+ if (previous_state == cp_layer_renderer_state_paused && visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->emit_signal_enum(VisionOSXRInterface::VISIONOS_XR_SIGNAL_SESSION_RESUMED);
+ }
+ [self renderFrame];
+ }
+ }
+ }
+}
+
+- (void)renderFrame {
+ safeDispatchSyncToMain(^{
+ if (!OS_AppleEmbedded::get_singleton()) {
+ return;
+ }
+ // Check state again after possible thread hop
+ cp_layer_renderer_state state = cp_layer_renderer_get_state(_layer_renderer);
+ if (state != cp_layer_renderer_state_running) {
+ return;
+ }
+ OS_AppleEmbedded::get_singleton()->iterate();
+ });
+}
+
+- (void)worldRecentered {
+ Ref visionos_xr_interface = VisionOSXRInterface::find_interface();
+ if (visionos_xr_interface.is_valid()) {
+ visionos_xr_interface->emit_signal_enum(VisionOSXRInterface::VISIONOS_XR_SIGNAL_POSE_RECENTERED);
+ }
+}
+
+@end
diff --git a/platform/visionos/main_visionos.mm b/platform/visionos/main_visionos.mm
index d0d2cfc8a132..9a89c1daccd9 100644
--- a/platform/visionos/main_visionos.mm
+++ b/platform/visionos/main_visionos.mm
@@ -31,7 +31,7 @@
#import "os_visionos.h"
#include "core/profiling/profiling.h"
-#import "drivers/apple_embedded/godot_app_delegate.h"
+#import "drivers/apple_embedded/godot_app_delegate_apple_embedded.h"
#import "drivers/apple_embedded/main_utilities.h"
#include "main/main.h"
diff --git a/platform/visionos/render_mode_visionos.h b/platform/visionos/render_mode_visionos.h
new file mode 100644
index 000000000000..8b43ceff622d
--- /dev/null
+++ b/platform/visionos/render_mode_visionos.h
@@ -0,0 +1,45 @@
+/**************************************************************************/
+/* render_mode_visionos.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+class RenderModeVisionOS {
+public:
+ enum Mode {
+ WINDOWED = 0,
+ COMPOSITOR_SERVICES = 1
+ };
+
+ static Mode get_mode();
+ static void *get_compositor_services_device();
+
+private:
+ RenderModeVisionOS() = delete;
+};
diff --git a/platform/visionos/render_mode_visionos.mm b/platform/visionos/render_mode_visionos.mm
new file mode 100644
index 000000000000..1e837d49620e
--- /dev/null
+++ b/platform/visionos/render_mode_visionos.mm
@@ -0,0 +1,44 @@
+/**************************************************************************/
+/* render_mode_visionos.mm */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "render_mode_visionos.h"
+
+#import "godot_app_delegate_service_visionos.h"
+
+#import
+#import
+
+RenderModeVisionOS::Mode RenderModeVisionOS::get_mode() {
+ return static_cast(GDTAppDelegateServiceVisionOS.renderMode);
+}
+
+void *RenderModeVisionOS::get_compositor_services_device() {
+ return (__bridge void *)cp_layer_renderer_get_device(GDTAppDelegateServiceVisionOS.layerRenderer);
+}
diff --git a/platform_methods.py b/platform_methods.py
index 7a702c08b04d..578f4d524863 100644
--- a/platform_methods.py
+++ b/platform_methods.py
@@ -322,6 +322,12 @@ def setup_swift_builder(env, apple_platform, sdk_path, current_path, bridging_he
elif apple_platform == "visionossimulator":
target_suffix = "xros26.0-simulator"
+ elif apple_platform == "tvos":
+ target_suffix = "tvos26.0"
+
+ elif apple_platform == "tvosimulator":
+ target_suffix = "tvos26.0-simulator"
+
else:
raise Exception("Invalid platform argument passed to detect_darwin_sdk_path")
@@ -342,6 +348,7 @@ def setup_swift_builder(env, apple_platform, sdk_path, current_path, bridging_he
bridging_header_path = current_path + "/" + bridging_header_filename
env["SWIFTC"] = frontend_path + " -frontend -c" # Swift compiler
env["SWIFTCFLAGS"] = [
+ "-warnings-as-errors",
"-cxx-interoperability-mode=default",
"-emit-object",
"-target",
@@ -363,6 +370,11 @@ def setup_swift_builder(env, apple_platform, sdk_path, current_path, bridging_he
SWIFTCFLAGS=[
"-resource-dir",
"/root/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift",
+ # The OSS Swift compiler doesn't auto-load cross-import overlays (e.g.
+ # _CompositorServices_SwiftUI, which provides `CompositorLayer` and
+ # `CompositorLayerConfiguration` when both CompositorServices and SwiftUI
+ # are imported). Enable them explicitly via a frontend flag.
+ "-enable-cross-import-overlays",
]
)
@@ -401,3 +413,4 @@ def generate_swift_action(source, target, env, for_signature):
env.Append(BUILDERS={"Swift": swift_builder})
env["BUILDERS"]["Library"].add_src_builder("Swift")
env["BUILDERS"]["Object"].add_action(".swift", Action(generate_swift_action, generator=1))
+ env["BUILDERS"]["Object"].emitter[".swift"] = methods.redirect_emitter
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 878409fe1cee..5c0288a786bb 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -218,10 +218,15 @@ RID RenderForwardMobile::RenderBufferDataForwardMobile::get_color_fbs(Framebuffe
RID vrs_texture;
#ifndef XR_DISABLED
- if (render_buffers->get_vrs_mode() == RSE::VIEWPORT_VRS_XR) {
- Ref interface = XRServer::get_singleton()->get_primary_interface();
- if (interface.is_valid() && RD::get_singleton()->vrs_get_method() == RD::VRS_METHOD_FRAGMENT_DENSITY_MAP && interface->get_vrs_texture_format() == XRInterface::XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP) {
- vrs_texture = interface->get_vrs_texture();
+ RSE::ViewportVRSMode vrs_mode = render_buffers->get_vrs_mode();
+ if (vrs_mode == RSE::VIEWPORT_VRS_XR) {
+ Ref xr_interface = XRServer::get_singleton()->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ bool use_vrs_fragment_density_map = RD::get_singleton()->vrs_get_method() == RD::VRS_METHOD_FRAGMENT_DENSITY_MAP && xr_interface->get_vrs_texture_format() == XRInterface::XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP;
+ bool use_vrs_rasterization_rate_map = RD::get_singleton()->vrs_get_method() == RD::VRS_METHOD_RASTERIZATION_RATE_MAP && xr_interface->get_vrs_texture_format() == XRInterface::XR_VRS_TEXTURE_FORMAT_RASTERIZATION_RATE_MAP;
+ if (use_vrs_fragment_density_map || use_vrs_rasterization_rate_map) {
+ vrs_texture = xr_interface->get_vrs_texture();
+ }
}
}
#endif // XR_DISABLED
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index ec85abb1a075..c1e70d147d85 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -2580,6 +2580,10 @@ uint32_t RenderingDevice::_texture_vrs_method_to_usage_bits() const {
return RDD::TEXTURE_USAGE_VRS_FRAGMENT_SHADING_RATE_BIT;
case VRS_METHOD_FRAGMENT_DENSITY_MAP:
return RDD::TEXTURE_USAGE_VRS_FRAGMENT_DENSITY_MAP_BIT;
+ case VRS_METHOD_RASTERIZATION_RATE_MAP:
+ // Rasterization rate map is not a real texture and it's readonly from shaders.
+ // Its usage is managed by the Metal rendering driver, so it doesn't need any usage bits.
+ return 0;
default:
return 0;
}
@@ -3397,6 +3401,8 @@ RDD::RenderPassID RenderingDevice::_render_pass_create(RenderingDeviceDriver *p_
}
}
+ // Note: We can ignore the case when the method is VRS_METHOD_RASTERIZATION_RATE_MAP, as
+ // the fragment_density_map_attachment_reference parameter is ignored by the Metal rendering driver
RDD::AttachmentReference fragment_density_map_attachment_reference;
if (p_vrs_method == VRS_METHOD_FRAGMENT_DENSITY_MAP && p_vrs_attachment >= 0) {
fragment_density_map_attachment_reference.attachment = p_vrs_attachment;
@@ -3425,6 +3431,8 @@ RDG::ResourceUsage RenderingDevice::_vrs_usage_from_method(VRSMethod p_method) {
return RDG::RESOURCE_USAGE_ATTACHMENT_FRAGMENT_SHADING_RATE_READ;
case VRS_METHOD_FRAGMENT_DENSITY_MAP:
return RDG::RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ;
+ case VRS_METHOD_RASTERIZATION_RATE_MAP:
+ return RDG::RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ;
default:
return RDG::RESOURCE_USAGE_NONE;
}
@@ -3436,6 +3444,10 @@ RDD::PipelineStageBits RenderingDevice::_vrs_stages_from_method(VRSMethod p_meth
return RDD::PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT;
case VRS_METHOD_FRAGMENT_DENSITY_MAP:
return RDD::PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT;
+ case VRS_METHOD_RASTERIZATION_RATE_MAP:
+ // Rasterization rate map is not a real texture and it's readonly from shaders.
+ // Its usage is managed by the Metal rendering driver, so it doesn't need any pipeline stage bits.
+ return RDD::PipelineStageBits(0);
default:
return RDD::PipelineStageBits(0);
}
@@ -3447,6 +3459,10 @@ RDD::TextureLayout RenderingDevice::_vrs_layout_from_method(VRSMethod p_method)
return RDD::TEXTURE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL;
case VRS_METHOD_FRAGMENT_DENSITY_MAP:
return RDD::TEXTURE_LAYOUT_FRAGMENT_DENSITY_MAP_ATTACHMENT_OPTIMAL;
+ case VRS_METHOD_RASTERIZATION_RATE_MAP:
+ // Rasterization rate map is not a real texture and it's readonly from shaders.
+ // Its usage is managed by the Metal rendering driver, so it doesn't need a layout.
+ return RDD::TEXTURE_LAYOUT_UNDEFINED;
default:
return RDD::TEXTURE_LAYOUT_UNDEFINED;
}
@@ -3459,6 +3475,8 @@ void RenderingDevice::_vrs_detect_method() {
vrs_method = VRS_METHOD_FRAGMENT_SHADING_RATE;
} else if (fdm_capabilities.attachment_supported) {
vrs_method = VRS_METHOD_FRAGMENT_DENSITY_MAP;
+ } else if (driver->has_feature(SUPPORTS_RASTERIZATION_RATE_MAP)) {
+ vrs_method = VRS_METHOD_RASTERIZATION_RATE_MAP;
}
switch (vrs_method) {
@@ -3470,6 +3488,12 @@ void RenderingDevice::_vrs_detect_method() {
vrs_format = DATA_FORMAT_R8G8_UNORM;
vrs_texel_size = Vector2i(32, 32).clamp(fdm_capabilities.min_texel_size, fdm_capabilities.max_texel_size);
break;
+ case VRS_METHOD_RASTERIZATION_RATE_MAP:
+ // Rasterization rate map is not a real texture. It's a opaque object that contains screen space distortion metadata.
+ // For the sake of consistency with other APIs, we wrap it as a texture.
+ vrs_format = DATA_FORMAT_R8_UINT;
+ vrs_texel_size = Vector2i(16, 16);
+ break;
default:
break;
}
@@ -3669,6 +3693,7 @@ RID RenderingDevice::framebuffer_create_multipass(const Vector &p_texture_a
attachments.resize(p_texture_attachments.size());
Size2i size;
bool size_set = false;
+ Size2i vrs_overridden_size;
for (int i = 0; i < p_texture_attachments.size(); i++) {
AttachmentFormat af;
Texture *texture = texture_owner.get_or_null(p_texture_attachments[i]);
@@ -3685,6 +3710,12 @@ RID RenderingDevice::framebuffer_create_multipass(const Vector &p_texture_a
vrs_attachment = i;
}
+ // Rasterization map enables a bigger logical viewport than the physical texture.
+ if (texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT && vrs_method == VRS_METHOD_RASTERIZATION_RATE_MAP) {
+ vrs_overridden_size.width = texture->width;
+ vrs_overridden_size.height = texture->height;
+ }
+
if (!size_set) {
size.width = texture->width;
size.height = texture->height;
@@ -3712,6 +3743,10 @@ RID RenderingDevice::framebuffer_create_multipass(const Vector &p_texture_a
ERR_FAIL_COND_V_MSG(!size_set, RID(), "All attachments unused.");
+ if (vrs_overridden_size != Size2i()) {
+ size = vrs_overridden_size;
+ }
+
FramebufferFormatID format_id = framebuffer_format_create_multipass(attachments, p_passes, p_view_count, vrs_attachment);
if (format_id == INVALID_ID) {
return RID();
@@ -6215,7 +6250,7 @@ void RenderingDevice::draw_list_draw_indirect(DrawListID p_list, bool p_use_indi
_check_transfer_worker_buffer(buffer);
}
-void RenderingDevice::draw_list_set_viewport(DrawListID p_list, const Rect2 &p_rect) {
+void RenderingDevice::draw_list_set_viewport(DrawListID p_list, const Rect2i &p_rect) {
ERR_FAIL_COND(!draw_list.active);
if (p_rect.get_area() == 0) {
@@ -8977,6 +9012,8 @@ bool RenderingDevice::has_feature(const Features p_feature) const {
case SUPPORTS_ATTACHMENT_VRS: {
const RDD::FragmentShadingRateCapabilities &fsr_capabilities = driver->get_fragment_shading_rate_capabilities();
const RDD::FragmentDensityMapCapabilities &fdm_capabilities = driver->get_fragment_density_map_capabilities();
+ // The VRS_METHOD_RASTERIZATION_RATE_MAP method is managed by the Metal rendering driver, so it doesn't
+ // report the SUPPORTS_ATTACHMENT_VRS feature, to avoid VRS texture creation logic.
return fsr_capabilities.attachment_supported || fdm_capabilities.attachment_supported;
}
default:
@@ -9705,6 +9742,7 @@ void RenderingDevice::_bind_methods() {
BIND_ENUM_CONSTANT(SUPPORTS_RAY_QUERY);
BIND_ENUM_CONSTANT(SUPPORTS_RAYTRACING_PIPELINE);
BIND_ENUM_CONSTANT(SUPPORTS_HDR_OUTPUT);
+ BIND_ENUM_CONSTANT(SUPPORTS_RASTERIZATION_RATE_MAP);
BIND_ENUM_CONSTANT(LIMIT_MAX_BOUND_UNIFORM_SETS);
BIND_ENUM_CONSTANT(LIMIT_MAX_FRAMEBUFFER_COLOR_ATTACHMENTS);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 9f191982ba48..f98387d64a83 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -303,6 +303,7 @@ class RenderingDevice : public RenderingDeviceCommons {
CALLBACK_RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE,
CALLBACK_RESOURCE_USAGE_ATTACHMENT_FRAGMENT_SHADING_RATE_READ,
CALLBACK_RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ,
+ CALLBACK_RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ,
CALLBACK_RESOURCE_USAGE_GENERAL,
CALLBACK_RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ,
CALLBACK_RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ_WRITE,
@@ -493,6 +494,7 @@ class RenderingDevice : public RenderingDeviceCommons {
VRS_METHOD_NONE,
VRS_METHOD_FRAGMENT_SHADING_RATE,
VRS_METHOD_FRAGMENT_DENSITY_MAP,
+ VRS_METHOD_RASTERIZATION_RATE_MAP,
};
private:
@@ -1556,7 +1558,7 @@ class RenderingDevice : public RenderingDeviceCommons {
void draw_list_draw(DrawListID p_list, bool p_use_indices, uint32_t p_instances = 1, uint32_t p_procedural_vertices = 0);
void draw_list_draw_indirect(DrawListID p_list, bool p_use_indices, RID p_buffer, uint32_t p_offset = 0, uint32_t p_draw_count = 1, uint32_t p_stride = 0);
- void draw_list_set_viewport(DrawListID p_list, const Rect2 &p_rect);
+ void draw_list_set_viewport(DrawListID p_list, const Rect2i &p_rect);
void draw_list_enable_scissor(DrawListID p_list, const Rect2 &p_rect);
void draw_list_disable_scissor(DrawListID p_list);
diff --git a/servers/rendering/rendering_device_commons.h b/servers/rendering/rendering_device_commons.h
index 6664d0c85cce..7b701f8e70ae 100644
--- a/servers/rendering/rendering_device_commons.h
+++ b/servers/rendering/rendering_device_commons.h
@@ -1036,6 +1036,7 @@ class RenderingDeviceCommons : public Object {
SUPPORTS_RAY_QUERY,
SUPPORTS_RAYTRACING_PIPELINE,
SUPPORTS_HDR_OUTPUT,
+ SUPPORTS_RASTERIZATION_RATE_MAP,
};
enum SubgroupOperations {
diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp
index 14b5277669c1..3e761d274696 100644
--- a/servers/rendering/rendering_device_graph.cpp
+++ b/servers/rendering/rendering_device_graph.cpp
@@ -105,6 +105,7 @@ bool RenderingDeviceGraph::_is_write_usage(ResourceUsage p_usage) {
case RESOURCE_USAGE_STORAGE_IMAGE_READ:
case RESOURCE_USAGE_ATTACHMENT_FRAGMENT_SHADING_RATE_READ:
case RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ:
+ case RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ:
case RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ:
return false;
case RESOURCE_USAGE_COPY_TO:
@@ -146,6 +147,10 @@ RDD::TextureLayout RenderingDeviceGraph::_usage_to_image_layout(ResourceUsage p_
return RDD::TEXTURE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL;
case RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ:
return RDD::TEXTURE_LAYOUT_FRAGMENT_DENSITY_MAP_ATTACHMENT_OPTIMAL;
+ case RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ:
+ // Rasterization rate map is not a real texture and it's readonly from shaders,
+ // so it doesn't need a texture layout
+ return RDD::TEXTURE_LAYOUT_UNDEFINED;
case RESOURCE_USAGE_GENERAL:
return RDD::TEXTURE_LAYOUT_GENERAL;
case RESOURCE_USAGE_NONE:
@@ -198,6 +203,10 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage
return RDD::BARRIER_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT;
case RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ:
return RDD::BARRIER_ACCESS_FRAGMENT_DENSITY_MAP_ATTACHMENT_READ_BIT;
+ case RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ:
+ // Rasterization rate map is not a real texture and it's readonly from shaders,
+ // so it doesn't need barrier access attributes
+ return RDD::BarrierAccessBits(0);
case RESOURCE_USAGE_GENERAL:
return RDD::BarrierAccessBits(RDD::BARRIER_ACCESS_MEMORY_READ_BIT | RDD::BARRIER_ACCESS_MEMORY_WRITE_BIT);
case RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ_WRITE:
diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h
index 110b3dcb4841..21d11793d5fe 100644
--- a/servers/rendering/rendering_device_graph.h
+++ b/servers/rendering/rendering_device_graph.h
@@ -173,6 +173,7 @@ class RenderingDeviceGraph {
RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE,
RESOURCE_USAGE_ATTACHMENT_FRAGMENT_SHADING_RATE_READ,
RESOURCE_USAGE_ATTACHMENT_FRAGMENT_DENSITY_MAP_READ,
+ RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ,
RESOURCE_USAGE_GENERAL,
RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ,
RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ_WRITE,
diff --git a/servers/xr/xr_interface.cpp b/servers/xr/xr_interface.cpp
index 1800c91dfe16..1241aa8f830b 100644
--- a/servers/xr/xr_interface.cpp
+++ b/servers/xr/xr_interface.cpp
@@ -115,6 +115,7 @@ void XRInterface::_bind_methods() {
BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_UNIFIED);
BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_FRAGMENT_SHADING_RATE);
BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP);
+ BIND_ENUM_CONSTANT(XR_VRS_TEXTURE_FORMAT_RASTERIZATION_RATE_MAP);
}
bool XRInterface::is_primary() {
diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h
index 5c8bd894d2c6..4f007bc30381 100644
--- a/servers/xr/xr_interface.h
+++ b/servers/xr/xr_interface.h
@@ -91,6 +91,7 @@ class XRInterface : public RefCounted {
XR_VRS_TEXTURE_FORMAT_UNIFIED,
XR_VRS_TEXTURE_FORMAT_FRAGMENT_SHADING_RATE,
XR_VRS_TEXTURE_FORMAT_FRAGMENT_DENSITY_MAP,
+ XR_VRS_TEXTURE_FORMAT_RASTERIZATION_RATE_MAP,
};
protected: