Skip to content
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
59 changes: 58 additions & 1 deletion ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
platform :ios, '13.0'

# Disable Mac Catalyst - ViroKit uses GLKit and ARCore which are iOS-only
ENV['ONLY_ACTIVE_ARCH'] = 'NO'

def common_pods
pod 'GVRAudioSDK', '1.140.0'
# ARCore SDK for Cloud Anchors, Geospatial, and Semantics support on iOS
# Requires Google Cloud API key in Info.plist
# These are OPTIONAL - ViroKit is built with weak linking so apps can
# choose whether to include ARCore pods based on their needs.
# Requires Google Cloud API key in Info.plist when used.
pod 'ARCore/CloudAnchors', '~> 1.51.0'
pod 'ARCore/Geospatial', '~> 1.51.0'
pod 'ARCore/Semantics', '~> 1.51.0'
Expand All @@ -17,3 +22,55 @@ target 'ViroKit' do
use_frameworks!
common_pods
end

# Post-install hook to enable weak linking for ARCore frameworks in ViroKit only
# This allows ViroKit to be used with or without ARCore SDK in the final app
post_install do |installer|
# Note: ARCore is distributed as sub-frameworks, not a single ARCore.framework
# We also need to weak link Firebase and Google frameworks that ARCore depends on
arcore_frameworks = ['ARCoreBase', 'ARCoreGARSession', 'ARCoreCloudAnchors',
'ARCoreGeospatial', 'ARCoreSemantics', 'ARCoreTFShared',
'FirebaseABTesting', 'FirebaseCore', 'FirebaseCoreInternal',
'FirebaseInstallations', 'FirebaseRemoteConfig',
'FirebaseRemoteConfigInterop', 'FirebaseSharedSwift',
'GTMSessionFetcher', 'GoogleToolboxForMac', 'nanopb',
'PromisesObjC']

# Only modify the ViroKit aggregate target's xcconfig files
# This converts strong framework links to weak for ARCore
installer.aggregate_targets.each do |aggregate_target|
# Only process ViroKit target
next unless aggregate_target.name.include?('ViroKit')

aggregate_target.xcconfigs.each do |config_name, config|
xcconfig_path = aggregate_target.xcconfig_path(config_name)
if File.exist?(xcconfig_path)
xcconfig_content = File.read(xcconfig_path)

# Replace -framework ARCore* with -weak_framework ARCore*
arcore_frameworks.each do |framework|
xcconfig_content.gsub!("-framework \"#{framework}\"", "-weak_framework \"#{framework}\"")
xcconfig_content.gsub!("-framework #{framework}", "-weak_framework #{framework}")
end

File.write(xcconfig_path, xcconfig_content)
end
end
end

# Also update the Pods-ViroKit xcconfig if it exists
pods_dir = installer.sandbox.root
['Debug', 'Release'].each do |config|
xcconfig_path = pods_dir.join("Target Support Files/Pods-ViroKit/Pods-ViroKit.#{config.downcase}.xcconfig")
if File.exist?(xcconfig_path)
xcconfig_content = File.read(xcconfig_path)

arcore_frameworks.each do |framework|
xcconfig_content.gsub!("-framework \"#{framework}\"", "-weak_framework \"#{framework}\"")
xcconfig_content.gsub!("-framework #{framework}", "-weak_framework #{framework}")
end

File.write(xcconfig_path, xcconfig_content)
end
end
end
2 changes: 1 addition & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,6 @@ SPEC CHECKSUMS:
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47

PODFILE CHECKSUM: 209a072bc18b67c05a44ce55b307315731b32c45
PODFILE CHECKSUM: a4961941dfe33b13591236859e595177a6efa929

COCOAPODS: 1.16.2
31 changes: 31 additions & 0 deletions ios/ViroKit.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ Pod::Spec.new do |s|
s.name = 'ViroKit'
s.version = '1.0'
s.summary = 'Framework containing the ViroRenderer'
s.description = <<-DESC
ViroKit is the core rendering framework for ViroReact.

ARCore SDK Integration (Optional):
ViroKit is built with weak linking for ARCore frameworks, making them optional.
To enable Cloud Anchors, Geospatial, or Scene Semantics features:

1. Add ARCore pods to your Podfile:
pod 'ARCore/CloudAnchors', '~> 1.51.0' # For Cloud Anchors
pod 'ARCore/Geospatial', '~> 1.51.0' # For Geospatial API
pod 'ARCore/Semantics', '~> 1.51.0' # For Scene Semantics

2. Add the ViroKit weak linking post_install hook to your Podfile.
See: https://github.com/ReactVision/viro#arcore-setup

Without these pods, ViroKit works normally but ARCore features return
isAvailable = false at runtime.
DESC
s.source = { :path => '.' } # source is required, but path will be defined in user's Podfile (this value will be ignored).
s.vendored_frameworks = 'ViroKit.framework'
s.homepage = 'https://reactvision.xyz'
Expand All @@ -10,4 +28,17 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.platform = :ios, '13.0'
s.dependency 'React'

# ARCore frameworks and Firebase dependencies are weak-linked via post_install hook
# This prevents linker errors when ARCore pods are not installed
# The following frameworks are weakly linked when ARCore is enabled:
# - ARCoreBase, ARCoreGARSession, ARCoreCloudAnchors, ARCoreGeospatial,
# ARCoreSemantics, ARCoreTFShared
# - FBLPromises, GoogleDataTransport, GoogleUtilities
# - FirebaseABTesting, FirebaseCore, FirebaseCoreInternal,
# FirebaseInstallations, FirebaseRemoteConfig, FirebaseRemoteConfigInterop,
# FirebaseSharedSwift
#
# The weak linking is applied conditionally via the Expo plugin or manually
# via post_install hook (see scripts/viro_post_install.rb for non-Expo users)
end
105 changes: 70 additions & 35 deletions ios/ViroKit/VROARFrameiOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "VROData.h"
#include "VROFieldOfView.h"
#include "VROARDepthMesh.h"
#include "VROMonocularDepthEstimator.h"

VROARFrameiOS::VROARFrameiOS(ARFrame *frame, VROViewport viewport, VROCameraOrientation orientation,
std::shared_ptr<VROARSessioniOS> session) :
Expand Down Expand Up @@ -223,6 +224,24 @@ std::shared_ptr<VROARPointCloud> VROARFrameiOS::getPointCloud() {
}

bool VROARFrameiOS::hasDepthData() const {
// Check for LiDAR depth first
if (hasLiDARDepth()) {
return true;
}

// Check for monocular depth estimation
std::shared_ptr<VROARSessioniOS> session = _session.lock();
if (session) {
auto depthEstimator = session->getMonocularDepthEstimator();
if (depthEstimator && depthEstimator->isAvailable()) {
return depthEstimator->getDepthTexture() != nullptr;
}
}

return false;
}

bool VROARFrameiOS::hasLiDARDepth() const {
if (@available(iOS 14.0, *)) {
return _frame.sceneDepth != nil;
}
Expand Down Expand Up @@ -252,47 +271,63 @@ std::shared_ptr<VROTexture> VROARFrameiOS::getDepthTexture() {
return _depthTexture;
}

if (@available(iOS 14.0, *)) {
ARDepthData *sceneDepth = _frame.sceneDepth;
if (sceneDepth == nil) {
return nullptr;
}

CVPixelBufferRef depthMap = sceneDepth.depthMap;
if (depthMap == nil) {
return nullptr;
}
std::shared_ptr<VROARSessioniOS> session = _session.lock();
bool preferMonocular = session && session->isPreferMonocularDepth();

// If preferMonocular is true, skip LiDAR and go directly to monocular depth
if (!preferMonocular) {
// Priority 1: LiDAR depth from ARKit (highest quality)
if (@available(iOS 14.0, *)) {
ARDepthData *sceneDepth = _frame.sceneDepth;
if (sceneDepth != nil) {
CVPixelBufferRef depthMap = sceneDepth.depthMap;
if (depthMap != nil) {
// Get the depth map dimensions
size_t width = CVPixelBufferGetWidth(depthMap);
size_t height = CVPixelBufferGetHeight(depthMap);

// Lock the buffer to access the data
CVPixelBufferLockBaseAddress(depthMap, kCVPixelBufferLock_ReadOnly);
void *baseAddress = CVPixelBufferGetBaseAddress(depthMap);

// The depth map is Float32 format
OSType formatType = CVPixelBufferGetPixelFormatType(depthMap);
if (formatType == kCVPixelFormatType_DepthFloat32 && baseAddress != nullptr) {
// Copy the depth data (we need to copy since the CVPixelBuffer will be released)
size_t dataSize = width * height * sizeof(float);
std::shared_ptr<VROData> depthData = std::make_shared<VROData>(baseAddress, dataSize, VRODataOwnership::Copy);
std::vector<std::shared_ptr<VROData>> dataVec = { depthData };

_depthTexture = std::make_shared<VROTexture>(VROTextureType::Texture2D,
VROTextureFormat::R32F,
VROTextureInternalFormat::R32F,
false, // not sRGB
VROMipmapMode::None,
dataVec,
(int)width, (int)height,
std::vector<uint32_t>());
}

// Get the depth map dimensions
size_t width = CVPixelBufferGetWidth(depthMap);
size_t height = CVPixelBufferGetHeight(depthMap);
CVPixelBufferUnlockBaseAddress(depthMap, kCVPixelBufferLock_ReadOnly);

// Lock the buffer to access the data
CVPixelBufferLockBaseAddress(depthMap, kCVPixelBufferLock_ReadOnly);
void *baseAddress = CVPixelBufferGetBaseAddress(depthMap);

// The depth map is Float32 format
OSType formatType = CVPixelBufferGetPixelFormatType(depthMap);
if (formatType == kCVPixelFormatType_DepthFloat32 && baseAddress != nullptr) {
// Copy the depth data (we need to copy since the CVPixelBuffer will be released)
size_t dataSize = width * height * sizeof(float);
std::shared_ptr<VROData> depthData = std::make_shared<VROData>(baseAddress, dataSize, VRODataOwnership::Copy);
std::vector<std::shared_ptr<VROData>> dataVec = { depthData };

_depthTexture = std::make_shared<VROTexture>(VROTextureType::Texture2D,
VROTextureFormat::R32F,
VROTextureInternalFormat::R32F,
false, // not sRGB
VROMipmapMode::None,
dataVec,
(int)width, (int)height,
std::vector<uint32_t>());
if (_depthTexture) {
return _depthTexture;
}
}
}
}
}

CVPixelBufferUnlockBaseAddress(depthMap, kCVPixelBufferLock_ReadOnly);
// Priority 2: Monocular depth estimation (fallback for non-LiDAR devices, or when preferred)
if (session) {
auto depthEstimator = session->getMonocularDepthEstimator();
if (depthEstimator && depthEstimator->isAvailable()) {
_depthTexture = depthEstimator->getDepthTexture();
return _depthTexture;
}
}

return _depthTexture;
return nullptr;
}

std::shared_ptr<VROTexture> VROARFrameiOS::getDepthConfidenceTexture() {
Expand Down
7 changes: 7 additions & 0 deletions ios/ViroKit/VROARFrameiOS.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ class API_AVAILABLE(ios(12.0)) VROARFrameiOS : public VROARFrame {
int getDepthImageWidth() const override;
int getDepthImageHeight() const override;

/*
Returns true if native LiDAR depth is available from ARKit.
This is distinct from hasDepthData() which may return true
for monocular depth estimation when LiDAR is not available.
*/
bool hasLiDARDepth() const;

/*
Returns the transform matrix to convert from camera texture coordinates
to depth texture coordinates. The depth map from ARKit may have a different
Expand Down
Loading
Loading