Skip to content

Commit

Permalink
feat: add visionOS support
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Jul 8, 2024
1 parent d0c9707 commit eee6ff4
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ if(NOT ANDROID)
add_subdirectory(Playground)
endif()

if((WIN32 AND NOT WINDOWS_STORE) OR (APPLE AND NOT IOS) OR (UNIX AND NOT ANDROID))
if((WIN32 AND NOT WINDOWS_STORE) OR (APPLE AND NOT IOS AND NOT VISIONOS) OR (UNIX AND NOT ANDROID))
add_subdirectory(UnitTests)
endif()

Expand Down
42 changes: 40 additions & 2 deletions Apps/Playground/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ if(APPLE)
"iOS/LibNativeBridge.mm")
set_source_files_properties(${SCRIPTS} ${BABYLON_SCRIPTS} ${DEPENDENCIES} PROPERTIES MACOSX_PACKAGE_LOCATION "Scripts")
set_source_files_properties(${REFERENCE_IMAGES} PROPERTIES MACOSX_PACKAGE_LOCATION "ReferenceImages")
set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES}
PRIVATE NativeCamera)
elseif(VISIONOS)
set(PLIST_FILE
"${CMAKE_CURRENT_LIST_DIR}/visionOS/Info.plist")
set(RESOURCE_FILES ${SCRIPTS})
set(SOURCES
${SOURCES}
"visionOS/App.swift"
"visionOS/LibNativeBridge.cpp"
"visionOS/LibNativeBridge.hpp"
"visionOS/BridgingHeader.h"
)
set_source_files_properties(${SCRIPTS} ${BABYLON_SCRIPTS} ${DEPENDENCIES} PROPERTIES MACOSX_PACKAGE_LOCATION "Scripts")
set_source_files_properties(${REFERENCE_IMAGES} PROPERTIES MACOSX_PACKAGE_LOCATION "ReferenceImages")
else()
set(PLIST_FILE "${CMAKE_CURRENT_LIST_DIR}/macOS/Info.plist")
set(STORYBOARD "${CMAKE_CURRENT_LIST_DIR}/macOS/Base.lproj/Main.storyboard")
Expand All @@ -50,8 +65,7 @@ if(APPLE)
set_source_files_properties(${REFERENCE_IMAGES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/ReferenceImages")
endif()
set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES}
PRIVATE ${JAVASCRIPTCORE_LIBRARY}
PRIVATE NativeCamera)
PRIVATE ${JAVASCRIPTCORE_LIBRARY})
set(RESOURCE_FILES ${STORYBOARD})
elseif(UNIX)
set(SOURCES
Expand Down Expand Up @@ -162,6 +176,30 @@ if(APPLE)
set(CMAKE_Swift_COMPILER_FORCED TRUE)
set(CMAKE_Swift_LANGUAGE_VERSION 4.0)
enable_language(Swift)
elseif(VISIONOS)
set_target_properties(Playground PROPERTIES
MACOSX_BUNDLE true
MACOSX_BUNDLE_INFO_PLIST "${PLIST_FILE}"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
RESOURCE "${RESOURCE_FILES}"

XCODE_ATTRIBUTE_XROS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.BabylonNative.Playground.visionOS"

XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_LIST_DIR}/visionOS/BridgingHeader.h"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_ATTRIBUTE_ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES YES
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"

# CMake seems to add a custom flag "-Wno-unknown-pragmas" to the Swift compiler. That flag is used for Clang,
# So we need to make sure we override it with nothing here in order to compile Swift.
XCODE_ATTRIBUTE_OTHER_SWIFT_FLAGS "")

# Swift support
set(CMAKE_Swift_COMPILER_FORCED TRUE)
set(CMAKE_Swift_LANGUAGE_VERSION 5.0)
enable_language(Swift)
else()
target_link_libraries(Playground PUBLIC "-framework MetalKit")

Expand Down
100 changes: 100 additions & 0 deletions Apps/Playground/visionOS/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 libNativeBridge: LibNativeBridge

init(_ layerRenderer: LayerRenderer) {
self.layerRenderer = layerRenderer
self.libNativeBridge = LibNativeBridge(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")

libNativeBridge.shutdown()
return
} else if layerRenderer.state == .paused {
layerRenderer.waitUntilRunning()
continue
} else {
autoreleasepool {
libNativeBridge.initialize()
libNativeBridge.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)
}
}
2 changes: 2 additions & 0 deletions Apps/Playground/visionOS/BridgingHeader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#include "LibNativeBridge.hpp"

33 changes: 33 additions & 0 deletions Apps/Playground/visionOS/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Need photo library permission for debug code to save photo captures</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationPreferredDefaultSceneSessionRole</key>
<string>UIWindowSceneSessionRoleApplication</string>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
</dict>
</plist>
98 changes: 98 additions & 0 deletions Apps/Playground/visionOS/LibNativeBridge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#import "LibNativeBridge.hpp"
#import <Babylon/AppRuntime.h>
#import <Babylon/Graphics/Device.h>
#import <Babylon/ScriptLoader.h>
#import <Babylon/Plugins/NativeEngine.h>
#import <Babylon/Plugins/NativeInput.h>
#import <Babylon/Plugins/NativeOptimizations.h>
#import <Babylon/Polyfills/Canvas.h>
#import <Babylon/Polyfills/Console.h>
#import <Babylon/Polyfills/Window.h>
#import <Babylon/Polyfills/XMLHttpRequest.h>

std::optional<Babylon::Graphics::Device> device{};
std::optional<Babylon::Graphics::DeviceUpdate> update{};
std::optional<Babylon::AppRuntime> runtime{};
std::optional<Babylon::Polyfills::Canvas> nativeCanvas{};
Babylon::Plugins::NativeInput* nativeInput{};

bool LibNativeBridge::initialize() {
if (m_initialized) {
return true;
}

Babylon::Graphics::Configuration graphicsConfig{};
graphicsConfig.Window = m_layerRenderer;
// Pass in visionOS default widht and height.
graphicsConfig.Width = static_cast<size_t>(2732);
graphicsConfig.Height = static_cast<size_t>(2048);

device.emplace(graphicsConfig);
update.emplace(device->GetUpdate("update"));

device->StartRenderingCurrentFrame();
update->Start();

runtime.emplace();

runtime->Dispatch([](Napi::Env env)
{
device->AddToJavaScript(env);

Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) {
NSLog(@"%s", message);
});

nativeCanvas.emplace(Babylon::Polyfills::Canvas::Initialize(env));

Babylon::Polyfills::Window::Initialize(env);

Babylon::Polyfills::XMLHttpRequest::Initialize(env);

Babylon::Plugins::NativeEngine::Initialize(env);

Babylon::Plugins::NativeOptimizations::Initialize(env);

nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(env);
});

Babylon::ScriptLoader loader{ *runtime };
loader.LoadScript("app:///Scripts/ammo.js");
loader.LoadScript("app:///Scripts/recast.js");
loader.LoadScript("app:///Scripts/babylon.max.js");
loader.LoadScript("app:///Scripts/babylonjs.loaders.js");
loader.LoadScript("app:///Scripts/babylonjs.materials.js");
loader.LoadScript("app:///Scripts/babylon.gui.js");
loader.LoadScript("app:///Scripts/experience.js");
m_initialized = true;
return true;
}

void LibNativeBridge::render() {
if (device && m_initialized)
{
update->Finish();
device->FinishRenderingCurrentFrame();
device->StartRenderingCurrentFrame();
update->Start();
}
}

void LibNativeBridge::shutdown() {
if (!m_initialized) {
return;
}

if (device)
{
update->Finish();
device->FinishRenderingCurrentFrame();
}

nativeInput = {};
nativeCanvas.reset();
runtime.reset();
update.reset();
device.reset();
m_initialized = false;
}
24 changes: 24 additions & 0 deletions Apps/Playground/visionOS/LibNativeBridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef BgfxAdapter_hpp
#define BgfxAdapter_hpp

#include <CompositorServices/CompositorServices.h>

class LibNativeBridge {
private:
bool m_initialized = false;
cp_layer_renderer_t m_layerRenderer = NULL;

public:
LibNativeBridge(cp_layer_renderer_t layerRenderer) : m_layerRenderer(layerRenderer) {
}

~LibNativeBridge() {
shutdown();
}

bool initialize(void);
void shutdown(void);
void render(void);
};

#endif /* BgfxAdapter_hpp */
38 changes: 38 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,44 @@ demo app, click on the project selector and find `Playground` in the list of pos
selections. The `Play` button will subsequently allow you to build, run, and debug
the selected Babylon Native demo app.

## **Building on macOS, Targeting visionOS**

_Follow the steps from [All Development Platforms](#all-development-platforms) before proceeding._

**Required Tools:** [Xcode 15](https://developer.apple.com/xcode/) or newer,
[Python 3.0](https://www.python.org/) or newer (required by dependencies)

For macOS development, CMake will generate a Makefile by default. It may be possible
to build Babylon Native for macOS using this approach, but only the Xcode method is
supported at present. To generate an Xcode project using CMake, you must specify the
correct build system generator for CMake to use. Additionally, you must tell CMake
what toolchain to use, which provides additional information about how to generate an
visionOS Xcode project correctly. Run the following command from the repository root:

```
cmake -B build/visionOS -G Xcode -D VISIONOS=ON
```

To enable bitcode support, add this option to the cmake command line parameters:

```
-D ENABLE_BITCODE=ON
```

CMake will generate a new `BabylonNative.xcodeproj` file in the specified build folder.
Open the project by double-clicking on it in Finder or by entering the following command:

```
open build/visionOS/BabylonNative.xcodeproj
```

To select which project to build with Xcode, select the correct project name in the
menu to the right of the greyed-out `Stop` button adjacent to the `Play` button in
the top-left corner of the Xcode window. For example, to build and run the Playground
demo app, click on the project selector and find `Playground` in the list of possible
selections. The `Play` button will subsequently allow you to build, run, and debug
the selected Babylon Native demo app.

## **Building on Windows, Targeting Android**

_Follow the steps from [All Development Platforms](#all-development-platforms) before proceeding._
Expand Down
Loading

0 comments on commit eee6ff4

Please sign in to comment.