From f36ad259a8b6ddab6103d1c63f241f6430573b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Mon, 13 May 2024 16:38:19 +0200 Subject: [PATCH] feat: add visionOS examples --- examples/common/entry/entry_visionos.swift | 101 +++++++++ examples/common/entry/swift_adapter.cpp | 198 ++++++++++++++++++ examples/common/entry/swift_adapter.hpp | 25 +++ examples/common/entry/swift_bridging_header.h | 1 + examples/runtime/xros-info.plist | 33 +++ scripts/example-common.lua | 12 ++ scripts/genie.lua | 15 +- 7 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 examples/common/entry/entry_visionos.swift create mode 100644 examples/common/entry/swift_adapter.cpp create mode 100644 examples/common/entry/swift_adapter.hpp create mode 100644 examples/common/entry/swift_bridging_header.h create mode 100644 examples/runtime/xros-info.plist diff --git a/examples/common/entry/entry_visionos.swift b/examples/common/entry/entry_visionos.swift new file mode 100644 index 0000000000..c637c4f4ec --- /dev/null +++ b/examples/common/entry/entry_visionos.swift @@ -0,0 +1,101 @@ +import SwiftUI +import CompositorServices + + +struct ContentStageConfiguration: CompositorLayerConfiguration { + func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { + configuration.depthFormat = .depth32Float + configuration.colorFormat = .bgra8Unorm_srgb + + let foveationEnabled = capabilities.supportsFoveation + configuration.isFoveationEnabled = foveationEnabled + + let options: LayerRenderer.Capabilities.SupportedLayoutsOptions = foveationEnabled ? [.foveationEnabled] : [] + let supportedLayouts = capabilities.supportedLayouts(options: options) + + configuration.layout = supportedLayouts.contains(.layered) ? .layered : .dedicated + } +} + +class Renderer { + let layerRenderer: LayerRenderer + var bgfxAdapter: BgfxAdapter + + init(_ layerRenderer: LayerRenderer) { + self.layerRenderer = layerRenderer + bgfxAdapter = BgfxAdapter(layerRenderer) + } + + func startRenderLoop() { + let renderThread = Thread { + self.renderLoop() + } + renderThread.name = "Render Thread" + renderThread.start() + } + + + func renderLoop() { + while true { + if layerRenderer.state == .invalidated { + print("Layer is invalidated") + + bgfxAdapter.shutdown() + return + } else if layerRenderer.state == .paused { + layerRenderer.waitUntilRunning() + continue + } else { + autoreleasepool { + bgfxAdapter.initialize() + bgfxAdapter.render() + } + } + } + } +} + + +@main +struct ExampleApp: App { + @State private var showImmersiveSpace = false + @State private var immersiveSpaceIsShown = false + + @Environment(\.openImmersiveSpace) var openImmersiveSpace + @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace + + var body: some Scene { + WindowGroup { + VStack { + Toggle("Show Immersive Space", isOn: $showImmersiveSpace) + .toggleStyle(.button) + .padding(.top, 50) + } + .onChange(of: showImmersiveSpace) { _, newValue in + Task { + if newValue { + switch await openImmersiveSpace(id: "ImmersiveSpace") { + case .opened: + immersiveSpaceIsShown = true + case .error, .userCancelled: + fallthrough + @unknown default: + immersiveSpaceIsShown = false + showImmersiveSpace = false + } + } else if immersiveSpaceIsShown { + await dismissImmersiveSpace() + immersiveSpaceIsShown = false + } + } + } + + } + ImmersiveSpace(id: "ImmersiveSpace") { + CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in + let renderer = Renderer(layerRenderer) + renderer.startRenderLoop() + } + }.immersionStyle(selection: .constant(.full), in: .full) + } +} diff --git a/examples/common/entry/swift_adapter.cpp b/examples/common/entry/swift_adapter.cpp new file mode 100644 index 0000000000..e70438e85b --- /dev/null +++ b/examples/common/entry/swift_adapter.cpp @@ -0,0 +1,198 @@ +#include "entry_p.h" + +#if ENTRY_CONFIG_USE_NATIVE && BX_PLATFORM_VISIONOS + +#include "swift_adapter.hpp" + +#include +#include +#include + +namespace entry +{ + struct MainThreadEntry + { + int m_argc; + const char* const* m_argv; + + static int32_t threadFunc(bx::Thread* _thread, void* _userData); + }; + + static WindowHandle s_defaultWindow = { 0 }; + + struct Context + { + Context(uint32_t _width, uint32_t _height) + { + static const char* const argv[] = { "visionos" }; + m_mte.m_argc = BX_COUNTOF(argv); + m_mte.m_argv = argv; + + m_eventQueue.postSizeEvent(s_defaultWindow, _width, _height); + + + // Prevent render thread creation. + bgfx::renderFrame(); + + m_thread.init(MainThreadEntry::threadFunc, &m_mte); + } + + + ~Context() + { + m_thread.shutdown(); + } + + MainThreadEntry m_mte; + bx::Thread m_thread; + void* m_window; + + EventQueue m_eventQueue; + }; + + static Context* s_ctx; + + int32_t MainThreadEntry::threadFunc(bx::Thread* _thread, void* _userData) + { + BX_UNUSED(_thread); + + if (_thread != NULL) { + _thread->setThreadName("Main Thread BGFX"); + } + + CFBundleRef mainBundle = CFBundleGetMainBundle(); + if (mainBundle != nil) + { + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); + if (resourcesURL != nil) + { + char path[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8*)path, PATH_MAX) ) + { + chdir(path); + } + + CFRelease(resourcesURL); + } + } + + MainThreadEntry* self = (MainThreadEntry*)_userData; + int32_t result = main(self->m_argc, self->m_argv); + return result; + } + + const Event* poll() + { + return s_ctx->m_eventQueue.poll(); + } + + const Event* poll(WindowHandle _handle) + { + return s_ctx->m_eventQueue.poll(_handle); + } + + void release(const Event* _event) + { + s_ctx->m_eventQueue.release(_event); + } + + WindowHandle createWindow(int32_t _x, int32_t _y, uint32_t _width, uint32_t _height, uint32_t _flags, const char* _title) + { + BX_UNUSED(_x, _y, _width, _height, _flags, _title); + WindowHandle handle = { UINT16_MAX }; + return handle; + } + + void destroyWindow(WindowHandle _handle) + { + BX_UNUSED(_handle); + } + + void setWindowPos(WindowHandle _handle, int32_t _x, int32_t _y) + { + BX_UNUSED(_handle, _x, _y); + } + + void setWindowSize(WindowHandle _handle, uint32_t _width, uint32_t _height) + { + BX_UNUSED(_handle, _width, _height); + } + + void setWindowTitle(WindowHandle _handle, const char* _title) + { + BX_UNUSED(_handle, _title); + } + + void setWindowFlags(WindowHandle _handle, uint32_t _flags, bool _enabled) + { + BX_UNUSED(_handle, _flags, _enabled); + } + + void toggleFullscreen(WindowHandle _handle) + { + BX_UNUSED(_handle); + } + + void setMouseLock(WindowHandle _handle, bool _lock) + { + BX_UNUSED(_handle, _lock); + } + + void* getNativeWindowHandle(WindowHandle _handle) + { + if (kDefaultWindowHandle.idx == _handle.idx) + { + return s_ctx->m_window; + } + + return NULL; + } + + void* getNativeDisplayHandle() + { + return NULL; + } + + bgfx::NativeWindowHandleType::Enum getNativeWindowHandleType() + { + return bgfx::NativeWindowHandleType::Default; + } + +} // namespace entry + +using namespace entry; + + + +bool BgfxAdapter::initialize(void) { + if (!m_initialized) { + // Set context width and height to default visionOS resolution. It's different for the headset and device. +#if TARGET_OS_SIMULATOR + s_ctx = new Context(2732, 2048); +#else + s_ctx = new Context(1920, 1824); +#endif + s_ctx->m_window = m_layerRenderer; + m_initialized = true; + } + + return m_initialized; +} + +void BgfxAdapter::shutdown(void) { + if (m_initialized) { + s_ctx->m_eventQueue.postExitEvent(); + s_ctx = NULL; + } + + m_initialized = false; +} + +void BgfxAdapter::render() { + if (!m_initialized) { + return; + } + bgfx::renderFrame(); +} + +#endif // BX_PLATFORM_VISIONOS diff --git a/examples/common/entry/swift_adapter.hpp b/examples/common/entry/swift_adapter.hpp new file mode 100644 index 0000000000..ade36e3331 --- /dev/null +++ b/examples/common/entry/swift_adapter.hpp @@ -0,0 +1,25 @@ +#ifndef BgfxAdapter_hpp +#define BgfxAdapter_hpp + +#include + +class BgfxAdapter { +private: + bool m_initialized = false; + cp_layer_renderer_t m_layerRenderer = NULL; + +public: + BgfxAdapter(cp_layer_renderer_t layerRenderer) : m_layerRenderer(layerRenderer) { + } + + ~BgfxAdapter() { + shutdown(); + } + + bool initialize(void); + void shutdown(void); + void render(void); +}; + +#endif /* BgfxAdapter_hpp */ + diff --git a/examples/common/entry/swift_bridging_header.h b/examples/common/entry/swift_bridging_header.h new file mode 100644 index 0000000000..734ee563c0 --- /dev/null +++ b/examples/common/entry/swift_bridging_header.h @@ -0,0 +1 @@ +#include "swift_adapter.hpp" diff --git a/examples/runtime/xros-info.plist b/examples/runtime/xros-info.plist new file mode 100644 index 0000000000..9accc805d6 --- /dev/null +++ b/examples/runtime/xros-info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Examples Debug + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + + diff --git a/scripts/example-common.lua b/scripts/example-common.lua index bc3ba5471d..7f19b6cbc9 100644 --- a/scripts/example-common.lua +++ b/scripts/example-common.lua @@ -91,6 +91,18 @@ project ("example-common") path.join(BGFX_DIR, "examples/common/**.mm"), } + configuration { "xros*" } + files { + path.join(BGFX_DIR, "examples/common/**.swift"), + path.join(BGFX_DIR, "examples/common/**.hpp"), + path.join(BGFX_DIR, "examples/common/**.modulemap"), + } + xcodeprojectopts { + SWIFT_VERSION = "5.0", + SWIFT_OBJC_BRIDGING_HEADER = path.join(BGFX_DIR, "examples/common/entry/swift_bridging_header.h"), + SWIFT_OBJC_INTEROP_MODE = "objcxx", + } + configuration { "winstore* or durango"} files { path.join(BGFX_DIR, "examples/common/**.cx"), diff --git a/scripts/genie.lua b/scripts/genie.lua index 477f8f9bfd..364f8369da 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -408,8 +408,8 @@ function exampleProjectDefaults() "-weak_framework Metal", } - configuration { "ios* or tvos*" } - kind "ConsoleApp" + configuration { "ios* or tvos* or xros*" } + kind "WindowedApp" linkoptions { "-framework CoreFoundation", "-framework Foundation", @@ -419,6 +419,11 @@ function exampleProjectDefaults() "-framework UIKit", "-weak_framework Metal", } + xcodecopyresources { + { "shaders/metal", { + os.matchfiles(path.join(BGFX_DIR, "examples/runtime/shaders/metal/**.bin")) + }} + } configuration { "xcode*", "ios" } kind "WindowedApp" @@ -432,6 +437,12 @@ function exampleProjectDefaults() path.join(BGFX_DIR, "examples/runtime/tvOS-Info.plist"), } + configuration { "xcode*", "xros" } + kind "WindowedApp" + files { + path.join(BGFX_DIR, "examples/runtime/xros-info.plist"), + } + configuration {} strip()