Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: add visionOS support #1384

Merged
merged 6 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
docEdub marked this conversation as resolved.
Show resolved Hide resolved
string(REPLACE "-Wall" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
string(REPLACE "-Werror" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
okwasniewski marked this conversation as resolved.
Show resolved Hide resolved
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"
okwasniewski marked this conversation as resolved.
Show resolved Hide resolved
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