Skip to content

Commit

Permalink
feat: add visionOS support (#1384)
Browse files Browse the repository at this point in the history
This PR introduces visionOS windowed rendering for Babylon Native.

NativeXr plugin integration will be a separate PR.

### Recording
https://github.com/user-attachments/assets/8cf0104b-3267-4fa7-9361-7351637cf114
  • Loading branch information
okwasniewski authored Aug 29, 2024
1 parent 1bc427c commit fdcd2f9
Show file tree
Hide file tree
Showing 14 changed files with 475 additions and 68 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
58 changes: 58 additions & 0 deletions Apps/Playground/AppleShared/GestureRecognizer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import UIKit

/**
* A very simple gesture recognizer. All that it does is to emulate the functionality found in other platforms
*/
class UIBabylonGestureRecognizer: UIGestureRecognizer {
// Callback for touch down events
private let _onTouchDown: (Int32, Int32, Int32)->Void
// Callback for touch movement events
private let _onTouchMove: (Int32, Int32, Int32)->Void
// Callback for touch up events
private let _onTouchUp: (Int32, Int32, Int32)->Void
// Table to track hashes of active touches
private var _activeTouchIds: Array<Int> = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

public init(target: Any?, onTouchDown: @escaping(Int32, Int32, Int32)->Void, onTouchMove: @escaping(Int32, Int32, Int32)->Void, onTouchUp: @escaping(Int32, Int32, Int32)->Void) {
_onTouchDown = onTouchDown
_onTouchMove = onTouchMove
_onTouchUp = onTouchUp

super.init(target: target, action: nil)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: -1) else { continue }
_activeTouchIds[deviceSlot] = touch.hash
let loc = touch.location(in: view)

_onTouchDown(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
}
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: touch.hash) else { continue }
let loc = touch.location(in: view)

_onTouchMove(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
}
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: touch.hash) else { continue }
let loc = touch.location(in: view)

_onTouchUp(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
_activeTouchIds[deviceSlot] = -1
}
}
}
60 changes: 52 additions & 8 deletions Apps/Playground/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,27 @@ if(APPLE)
"iOS/AppDelegate.swift"
"iOS/ViewController.swift"
"iOS/LibNativeBridge.h"
"iOS/LibNativeBridge.mm")
"iOS/LibNativeBridge.mm"
"AppleShared/GestureRecognizer.swift")
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.mm"
"visionOS/LibNativeBridge.h"
"AppleShared/GestureRecognizer.swift")
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")
set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PRIVATE NativeCamera)
set(RESOURCE_FILES ${STORYBOARD})
set(SOURCES
${SOURCES}
Expand All @@ -49,11 +64,12 @@ if(APPLE)
set_source_files_properties(${SCRIPTS} ${BABYLON_SCRIPTS} ${DEPENDENCIES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/Scripts")
set_source_files_properties(${REFERENCE_IMAGES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/ReferenceImages")
endif()
set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES}
PRIVATE ${JAVASCRIPTCORE_LIBRARY}
PRIVATE NativeCamera)
set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} PRIVATE ${JAVASCRIPTCORE_LIBRARY})
set(RESOURCE_FILES ${STORYBOARD})

# Remove -Wall and -Werror from the debug flags to make simulators work.
string(REPLACE "-Wall" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
string(REPLACE "-Werror" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
elseif(UNIX)
set(SOURCES
${SOURCES}
Expand Down Expand Up @@ -153,16 +169,42 @@ if(APPLE)
XCODE_ATTRIBUTE_SWIFT_VERSION "4.0"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_LIST_DIR}/iOS/LibNativeBridge.h"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks"
XCODE_ATTRIBUTE_ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES YES

# 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 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/LibNativeBridge.h"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/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 4.0)
enable_language(Swift)
# 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 Expand Up @@ -209,3 +251,5 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SCRIPTS})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Playground)

set_property(TARGET Playground PROPERTY XCODE_GENERATE_SCHEME YES)
set_property(TARGET Playground PROPERTY XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED YES)
57 changes: 0 additions & 57 deletions Apps/Playground/iOS/ViewController.swift
Original file line number Diff line number Diff line change
@@ -1,63 +1,6 @@
import UIKit
import MetalKit

/**
* A very simple gesture recognizer. All that it does is to emulate the functionality found in other platforms
*/
class UIBabylonGestureRecognizer: UIGestureRecognizer {
// Callback for touch down events
private let _onTouchDown: (Int32, Int32, Int32)->Void
// Callback for touch movement events
private let _onTouchMove: (Int32, Int32, Int32)->Void
// Callback for touch up events
private let _onTouchUp: (Int32, Int32, Int32)->Void
// Table to track hashes of active touches
private var _activeTouchIds: Array<Int> = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

public init(target: Any?, onTouchDown: @escaping(Int32, Int32, Int32)->Void, onTouchMove: @escaping(Int32, Int32, Int32)->Void, onTouchUp: @escaping(Int32, Int32, Int32)->Void) {
_onTouchDown = onTouchDown
_onTouchMove = onTouchMove
_onTouchUp = onTouchUp

super.init(target: target, action: nil)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: -1) else { continue }
_activeTouchIds[deviceSlot] = touch.hash
let loc = touch.location(in: view)

_onTouchDown(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
}
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: touch.hash) else { continue }
let loc = touch.location(in: view)

_onTouchMove(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
}
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)

for touch in touches {
guard let deviceSlot = _activeTouchIds.firstIndex(of: touch.hash) else { continue }
let loc = touch.location(in: view)

_onTouchUp(Int32(deviceSlot), Int32(loc.x), Int32(loc.y))
_activeTouchIds[deviceSlot] = -1
}
}
}

class ViewController: UIViewController {

var mtkView: MTKView!
Expand Down
77 changes: 77 additions & 0 deletions Apps/Playground/visionOS/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import SwiftUI

class MetalView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func setupMetalLayer() {
guard let bridge = LibNativeBridge.sharedInstance() else { return }

if bridge.metalLayer != nil {
return
}

self.addGestureRecognizer(
UIBabylonGestureRecognizer(
target: self,
onTouchDown: bridge.setTouchDown,
onTouchMove: bridge.setTouchMove,
onTouchUp: bridge.setTouchUp
)
)
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true

bridge.metalLayer = self.metalLayer

let scale = UITraitCollection.current.displayScale
bridge.initialize(withWidth: Int(self.bounds.width * scale), height: Int(self.bounds.height * scale))
}

var metalLayer: CAMetalLayer {
return layer as! CAMetalLayer
}

override class var layerClass: AnyClass {
return CAMetalLayer.self
}

override func layoutSubviews() {
super.layoutSubviews()
setupMetalLayer()
updateDrawableSize()
}

private func updateDrawableSize() {
let scale = UITraitCollection.current.displayScale
LibNativeBridge.sharedInstance().drawableWillChangeSize(withWidth: Int(bounds.width * scale), height: Int(bounds.height * scale))
metalLayer.drawableSize = CGSize(width: bounds.width * scale, height: bounds.height * scale)
}
}

struct MetalViewRepresentable: UIViewRepresentable {
typealias UIViewType = MetalView

func makeUIView(context: Context) -> MetalView {
MetalView(frame: .zero)
}

func updateUIView(_ uiView: MetalView, context: Context) {}
}


@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
MetalViewRepresentable()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
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>
24 changes: 24 additions & 0 deletions Apps/Playground/visionOS/LibNativeBridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
#import <CompositorServices/CompositorServices.h>

@class CAMetalLayer;

@interface LibNativeBridge : NSObject

@property (nonatomic, assign, getter=isInitialized) BOOL initialized;
@property (nonatomic, strong) CAMetalLayer *metalLayer;

+ (instancetype)sharedInstance;

- (void)setTouchDown:(int)pointerId x:(int)inX y:(int)inY;
- (void)setTouchMove:(int)pointerId x:(int)inX y:(int)inY;
- (void)setTouchUp:(int)pointerId x:(int)inX y:(int)inY;

- (void)drawableWillChangeSizeWithWidth:(NSInteger)width height:(NSInteger)height;

- (bool)initializeWithWidth:(NSInteger)width height:(NSInteger)height;
- (void)shutdown;
- (void)render;

@end

Loading

0 comments on commit fdcd2f9

Please # to comment.