diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index b6ec3f8c9d68..220f5d60c1e6 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -2743,6 +2743,9 @@ Support for high dynamic range (HDR) output. + + Support for rasterization rate maps 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 ede1ce31e789..1d83b5431cf6 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/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 9e5764cefb82..a071ae120863 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" 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..0662e0e8ab73 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; } 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 97% rename from drivers/apple_embedded/app_delegate_service.mm rename to drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm index 4e3ce1b443f4..9844319deb7b 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 { 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..922c5e2f176c --- /dev/null +++ b/drivers/apple_embedded/godot_renderer.mm @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* 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" + +#import "display_server_apple_embedded.h" +#import "os_apple_embedded.h" + +#include "core/config/project_settings.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..89983aec0a34 100644 --- a/drivers/apple_embedded/godot_view_apple_embedded.mm +++ b/drivers/apple_embedded/godot_view_apple_embedded.mm @@ -179,7 +179,7 @@ - (void)stopRendering { self.isActive = NO; - print_verbose("Stop animation!"); + print_verbose("Stop rendering"); if (self.useCADisplayLink) { [self.displayLink invalidate]; @@ -199,7 +199,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 +237,7 @@ - (void)drawView { return; } - if ([self.renderer setupView:self]) { + if ([self.renderer setUp]) { return; } diff --git a/drivers/apple_embedded/godot_view_renderer.h b/drivers/apple_embedded/godot_view_renderer.h index 1d409d9b4500..2b2748b1887f 100644 --- a/drivers/apple_embedded/godot_view_renderer.h +++ b/drivers/apple_embedded/godot_view_renderer.h @@ -30,17 +30,11 @@ #pragma once +#import "godot_renderer.h" #import -@protocol GDTViewRendererProtocol +@interface GDTViewRenderer : GDTRenderer -@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; - -- (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..7f36ad92ba1d 100644 --- a/drivers/apple_embedded/godot_view_renderer.mm +++ b/drivers/apple_embedded/godot_view_renderer.mm @@ -44,76 +44,15 @@ #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 c1dfa5304c01..5f7ed5163535 100644 --- a/drivers/apple_embedded/os_apple_embedded.mm +++ b/drivers/apple_embedded/os_apple_embedded.mm @@ -40,7 +40,7 @@ #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/godot_app_delegate_service_apple_embedded.h" #import "drivers/apple_embedded/display_server_apple_embedded.h" #import "drivers/apple_embedded/godot_view_apple_embedded.h" #import "drivers/apple_embedded/godot_view_controller.h" 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 a749de8259b4..49a827b2cbb5 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 d417fa8ba87b..a9cdb694de58 100644 --- a/drivers/metal/rendering_context_driver_metal.h +++ b/drivers/metal/rendering_context_driver_metal.h @@ -105,7 +105,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 a989f99b5f29..8a973a725cb7 100644 --- a/drivers/metal/rendering_device_driver_metal.cpp +++ b/drivers/metal/rendering_device_driver_metal.cpp @@ -61,16 +61,29 @@ #include "drivers/metal/rendering_context_driver_metal.h" #include "drivers/metal/rendering_shader_container_metal.h" -#include +#include +#include +#include #include #include - +#include #include #ifndef MTLGPUAddress 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 +544,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 +1027,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 +1058,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); } @@ -2675,6 +2706,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/editor/export/editor_export_platform_apple_embedded.cpp b/editor/export/editor_export_platform_apple_embedded.cpp index 2f9db30283ef..8c859b9c26c7 100644 --- a/editor/export/editor_export_platform_apple_embedded.cpp +++ b/editor/export/editor_export_platform_apple_embedded.cpp @@ -1629,6 +1629,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); } diff --git a/editor/export/editor_export_platform_apple_embedded.h b/editor/export/editor_export_platform_apple_embedded.h index 3a346ef72bb7..295af5b7e328 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/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 @@ CADisableMinimumFrameDurationOnPhone UIApplicationSceneManifest + $application_scene_manifest_default_session_role UIApplicationSupportsMultipleScenes UISceneConfigurations @@ -71,6 +72,7 @@ Default Configuration + $application_scene_manifest_immersive_configuration 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..fb20926f90f5 --- /dev/null +++ b/modules/visionos_xr/visionos_xr_interface.h @@ -0,0 +1,230 @@ +/**************************************************************************/ +/* 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 { + 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..9d759e1c5ffa --- /dev/null +++ b/modules/visionos_xr/visionos_xr_interface.mm @@ -0,0 +1,621 @@ +/**************************************************************************/ +/* 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() { + // and make sure we cleanup if we haven't already + 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 a313442e2ae3..46d23b9f8190 100644 --- a/platform/ios/SCsub +++ b/platform/ios/SCsub @@ -2,26 +2,42 @@ 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) + combine_command = env_ios.CommandNoCache( "#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], env.Run(combine_libs_apple_embedded) ) 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 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 0297bed05760..fc89c7451b4b 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..f4e8b4febd0d --- /dev/null +++ b/platform/visionos/godot_app_delegate_service_visionos.h @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* 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 "drivers/apple_embedded/godot_app_delegate_service_apple_embedded.h" +#import "render_mode_visionos.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..a2d904b75936 --- /dev/null +++ b/platform/visionos/godot_compositor_services_renderer.h @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* 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..a9c811c745dc --- /dev/null +++ b/platform/visionos/godot_compositor_services_renderer.mm @@ -0,0 +1,114 @@ +/**************************************************************************/ +/* 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 2b5aaa61b8f0..4fc85d84940b 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -287,6 +287,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", @@ -346,3 +347,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 1232ad33537b..4927526b7ec8 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 03b782c19584..75cc3ad4b621 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -2201,6 +2201,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; + // 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. + case VRS_METHOD_RASTERIZATION_RATE_MAP: + return 0; default: return 0; } @@ -3014,6 +3018,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; @@ -3042,6 +3048,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; } @@ -3053,6 +3061,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; + // 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. + case VRS_METHOD_RASTERIZATION_RATE_MAP: + return RDD::PipelineStageBits(0); default: return RDD::PipelineStageBits(0); } @@ -3064,6 +3076,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; + // 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. + case VRS_METHOD_RASTERIZATION_RATE_MAP: + return RDD::TEXTURE_LAYOUT_UNDEFINED; default: return RDD::TEXTURE_LAYOUT_UNDEFINED; } @@ -3076,6 +3092,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) { @@ -3087,6 +3105,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; + // 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. + case VRS_METHOD_RASTERIZATION_RATE_MAP: + vrs_format = DATA_FORMAT_R8_UINT; + vrs_texel_size = Vector2i(16, 16); + break; default: break; } @@ -3286,6 +3310,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]); @@ -3302,6 +3327,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; @@ -3329,6 +3360,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(); @@ -5624,7 +5659,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) { @@ -8310,6 +8345,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: @@ -9021,6 +9058,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 5c7c9cf2417f..34c63d9b8a37 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -295,6 +295,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_BUILD_INPUT, CALLBACK_RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ, @@ -485,6 +486,7 @@ class RenderingDevice : public RenderingDeviceCommons { VRS_METHOD_NONE, VRS_METHOD_FRAGMENT_SHADING_RATE, VRS_METHOD_FRAGMENT_DENSITY_MAP, + VRS_METHOD_RASTERIZATION_RATE_MAP, }; private: @@ -1457,7 +1459,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 f05aa7b179e7..b31dba1f8d93 100644 --- a/servers/rendering/rendering_device_commons.h +++ b/servers/rendering/rendering_device_commons.h @@ -989,6 +989,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 f371c682babc..b42c2d5075e2 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_BUILD_INPUT: case RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ: return false; @@ -147,6 +148,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; + // Rasterization rate map is not a real texture and it's readonly from shaders, + // so it doesn't need a texture layout + case RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ: + return RDD::TEXTURE_LAYOUT_UNDEFINED; case RESOURCE_USAGE_GENERAL: return RDD::TEXTURE_LAYOUT_GENERAL; case RESOURCE_USAGE_NONE: @@ -203,6 +208,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; + // Rasterization rate map is not a real texture and it's readonly from shaders, + // so it doesn't need barrier access attributes + case RESOURCE_USAGE_ATTACHMENT_RASTERIZATION_RATE_MAP_READ: + 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 ce1be7eefa63..26fa6b1b9c22 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -172,6 +172,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_BUILD_INPUT, RESOURCE_USAGE_ACCELERATION_STRUCTURE_READ, 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: