diff --git a/CHANGELOG.md b/CHANGELOG.md index 5866d196e..1fa9a528a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - **Quest: optional hand occlusion in passthrough (`VROHandOcclusionOpenXR`, `VROSceneRendererOpenXR`, `VRORenderContext`, `VROPortalTreeRenderPass`, `ViroViewOpenXR`, `Renderer`).** In mixed reality the real hands exist only in the passthrough video beneath the projection layer, so anything virtual in front of a hand hid it, and a user carrying a part could not see what they were about to pinch. Each eye, before the scene is drawn, the tracked hands are now drawn into the depth buffer only: scene pixels behind a hand fail the depth test and are never written, the layer stays transparent there, and the passthrough hand shows through. Content in front of the hand still covers it, and translucent content behind it is clipped too. No depth submission or environment-depth API is used, so it works on Quest 2, 3, 3S and Pro. The hand geometry is the runtime's skinned hand mesh (`XR_FB_hand_tracking_mesh`, now enabled when offered), skinned on the GPU from the joints located at the frame's predicted display time; without the extension, or in `capsules` mode, it is about 29 capsules between the `XR_EXT_hand_tracking` joints sized from their radii. Either is grown by `inflateMm` (default 2) to cover small tracking misfit, and a hand that drops out of tracking keeps its last pose for 100 ms so the hole does not flicker. It is off by default and costs nothing while off. `ViroViewOpenXR.setHandOcclusionEnabled`, `setHandOcclusionMode("mesh" | "capsules")` and `setHandOcclusionInflateMm` change it at runtime, applying on the next frame, and are cached and re-applied when set before the renderer exists, like the passthrough props. It only draws while passthrough and hand tracking are both on; otherwise it is a no-op and logs once. The pass runs from a new depth-prepass slot on `VRORenderContext` that the base render pass calls right after clearing its target, because on Quest the scene is drawn into the HDR target, not the swapchain image. A per-node opt-out (`drawOverHands`, for wrist menus and held virtual tools) is not part of this change. ViroReact sets them through `handOcclusion`, `handOcclusionMode` and `handOcclusionInflateMm` on the VR/XR scene navigators. +- **ViroKit builds for the iOS Simulator (arm64 and x86_64).** `ios/build_ios_simulator_libs.sh` adds the arm64 simulator slices it was missing: LinearMath (Bullet 2.87) and protobuf-lite 3.2.0 built from source, and GVR Audio relabelled by `retag_for_simulator.py`, since it is closed source and its arm64 code is the same on device and simulator. ### Changed @@ -42,6 +43,7 @@ - **A depth drop-out no longer replaces the fused room with plane polygons (`VROARWorldMesh`).** ARCore's motion-stereo depth is not continuous: on a Xiaomi 24117RN76L it stops for a frame at a time, several times a second, and the plane fallback is then the only source left. Publishing those few polygons over an accumulated room made the mesh flicker between 90,532 vertices and 41, taking the physics body and `getWorldMeshStats()` with it — and a VPS Lite snapshot that landed in one of the gaps uploaded the polygons instead of the room. Once a mesh is accumulated, a frame with no depth keeps it rather than falling back; `resetWorldMesh()` is how an app starts over. Found on a device, not in the tests. - **Fusion stops while tracking is limited (`VROARWorldMesh`).** Integrating across a relocalisation drags the old room onto the new origin. The frame is dropped and the room fused so far stays on screen, rather than being replaced by that single view. +- **The ViroKit build stages into viro as `ViroKit.xcframework` (`ViroKit.podspec`, "Lipo Framework and Copy to Bridge").** The phase replaces only the slice it built, skips a simulator build lacking arm64 or x86_64 (an app's Release simulator build asks for both, so such a slice would break it), and runs after `[CP] Copy Pods Resources`, so the ARCore resource bundles are in every copy rather than surviving only from an earlier one. The simulator no longer finds the device `libreactvisioncca.a` first, and the Jenkins phase makes its universal framework only when the two builds share no architecture. ### Fixed diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 882004492..45aa965c8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -37,7 +37,7 @@ - **A click goes to the node drawn on top,** and a click in open space reaches the controller's own delegates again. - **Text sits where its vertical alignment puts it,** and a back-culled draw after a front-culled one culls back faces again. - **Video:** a paused iOS video stays paused and its late callbacks no longer reach a freed texture; Android play and pause keep their order. -- **iOS:** `ViroKit.framework` targets iOS 15.1. +- **iOS:** ViroKit targets iOS 15.1, and builds for the iOS Simulator (arm64 and x86_64) as well as the device. The copy into viro now makes `ViroKit.xcframework`, replacing only the slice just built. `ios/build_ios_simulator_libs.sh` adds the simulator builds of LinearMath, protobuf-lite and GVR Audio that ViroKit links. - **Removed:** the macOS target, which no longer linked and nothing built, and dead code (an unused metrics client, the API key validator, the OpenCV headers, an old OpenXR loader). See `CHANGELOG.md` for every change. diff --git a/android/sharedCode/src/main/jniLibs/arm64-v8a/libreactvisioncca.so b/android/sharedCode/src/main/jniLibs/arm64-v8a/libreactvisioncca.so index 7f06cf7b3..83a1259c4 100755 Binary files a/android/sharedCode/src/main/jniLibs/arm64-v8a/libreactvisioncca.so and b/android/sharedCode/src/main/jniLibs/arm64-v8a/libreactvisioncca.so differ diff --git a/android/sharedCode/src/main/jniLibs/armeabi-v7a/libreactvisioncca.so b/android/sharedCode/src/main/jniLibs/armeabi-v7a/libreactvisioncca.so index a08ad7d7b..5694a34b4 100755 Binary files a/android/sharedCode/src/main/jniLibs/armeabi-v7a/libreactvisioncca.so and b/android/sharedCode/src/main/jniLibs/armeabi-v7a/libreactvisioncca.so differ diff --git a/ios/Libraries/GVRAudioSDK/simulator/libGVRAudioSDK.a b/ios/Libraries/GVRAudioSDK/simulator/libGVRAudioSDK.a new file mode 100755 index 000000000..6534817b9 Binary files /dev/null and b/ios/Libraries/GVRAudioSDK/simulator/libGVRAudioSDK.a differ diff --git a/ios/Libraries/bullet/x86_64/libLinearMath.a b/ios/Libraries/bullet/x86_64/libLinearMath.a index b797775fa..62a582af0 100644 Binary files a/ios/Libraries/bullet/x86_64/libLinearMath.a and b/ios/Libraries/bullet/x86_64/libLinearMath.a differ diff --git a/ios/Libraries/protobuf/x86_64/libprotobuf-lite.a b/ios/Libraries/protobuf/x86_64/libprotobuf-lite.a index 88dde7996..87ebc055f 100644 Binary files a/ios/Libraries/protobuf/x86_64/libprotobuf-lite.a and b/ios/Libraries/protobuf/x86_64/libprotobuf-lite.a differ diff --git a/ios/Libraries/reactvisioncca/arm64/libreactvisioncca.a b/ios/Libraries/reactvisioncca/arm64/libreactvisioncca.a index ed3f78381..0658a81e2 100644 Binary files a/ios/Libraries/reactvisioncca/arm64/libreactvisioncca.a and b/ios/Libraries/reactvisioncca/arm64/libreactvisioncca.a differ diff --git a/ios/Libraries/reactvisioncca/arm64_simulator/libreactvisioncca.a b/ios/Libraries/reactvisioncca/arm64_simulator/libreactvisioncca.a index d9b6540d9..b4a6325d4 100644 Binary files a/ios/Libraries/reactvisioncca/arm64_simulator/libreactvisioncca.a and b/ios/Libraries/reactvisioncca/arm64_simulator/libreactvisioncca.a differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4653ce74c..359ee0c78 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -109,6 +109,6 @@ SPEC CHECKSUMS: nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273 -PODFILE CHECKSUM: 11051fadc97488292feb7ba17863ebdb04f993ee +PODFILE CHECKSUM: c1ece960ba10d0cc22cbc2588a90184bd0d00fa7 COCOAPODS: 1.17.0 diff --git a/ios/ViroKit.podspec b/ios/ViroKit.podspec index b181b0aa0..f2cbd03e1 100644 --- a/ios/ViroKit.podspec +++ b/ios/ViroKit.podspec @@ -21,7 +21,8 @@ Pod::Spec.new do |s| 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' + # Device (ios-arm64) and simulator (ios-arm64_x86_64-simulator) slices. + s.vendored_frameworks = 'ViroKit.xcframework' s.homepage = 'https://reactvision.xyz' s.license = {:type => 'Copyright', :text => "Copyright 2025 ReactVision" } s.author = 'ReactVision' diff --git a/ios/ViroRenderer.xcodeproj/project.pbxproj b/ios/ViroRenderer.xcodeproj/project.pbxproj index 65a8948d7..92e79b9de 100644 --- a/ios/ViroRenderer.xcodeproj/project.pbxproj +++ b/ios/ViroRenderer.xcodeproj/project.pbxproj @@ -4520,9 +4520,9 @@ 8FD4DECA1C18ECD4003DC425 /* Headers */, 8FD4DECB1C18ECD4003DC425 /* Resources */, 8AE44C932021084A00CE8394 /* Copy GLM Files to Framework */, + F889831C70414DF33A563C8A /* [CP] Copy Pods Resources */, 8A8B69DD2036344C00D98723 /* Lipo Framework and Copy to Bridge */, E82CFD5E20AECB790091154F /* Lipo Framework and Copy to Bridge (build intermediates for Jenkins) */, - F889831C70414DF33A563C8A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -4914,13 +4914,14 @@ files = ( ); inputPaths = ( + "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)", ); name = "Lipo Framework and Copy to Bridge"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "VIRO_FRAMEWORK_PATH=$PROJECT_DIR/../../viro/ios/dist/ViroRenderer \n \nmkdir -p $VIRO_FRAMEWORK_PATH \ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_FRAMEWORK_PATH \n \n# Copy over the podspec \ncp -r $PROJECT_DIR/ViroKit.podspec $PROJECT_DIR/../../viro/ios/dist/ViroRenderer \n"; + shellScript = "# Stage the build into viro as ViroKit.xcframework. Only this platform's slice is replaced, so a\n# device build and a simulator build can run in either order and each keeps the other's slice.\n# A simulator slice must carry arm64 and x86_64: an app's Release simulator build asks for both,\n# and CocoaPods will not pick a slice that lacks one. Builds of the active arch only are skipped.\n# Info.plist is an input file: without it a clean build can copy the framework before its plist exists.\nif [ \"$PLATFORM_NAME\" = iphonesimulator ]; then\n case \" $ARCHS \" in *\" arm64 \"*) ;; *) echo \"warning: not staging a simulator slice without arm64 ($ARCHS)\"; exit 0 ;; esac\n case \" $ARCHS \" in *\" x86_64 \"*) ;; *) echo \"warning: not staging a simulator slice without x86_64 ($ARCHS)\"; exit 0 ;; esac\nfi\nVIRO_FRAMEWORK_PATH=\"$PROJECT_DIR/../../viro/ios/dist/ViroRenderer\"\nXCFRAMEWORK=\"$VIRO_FRAMEWORK_PATH/ViroKit.xcframework\"\nSTAGE=\"$TARGET_TEMP_DIR/xcframework-slices\"\nset -e\nrm -rf \"$STAGE\"\nmkdir -p \"$STAGE/$PLATFORM_NAME\" \"$VIRO_FRAMEWORK_PATH\"\ncp -R \"$TARGET_BUILD_DIR/ViroKit.framework\" \"$STAGE/$PLATFORM_NAME/\"\nset -- -framework \"$STAGE/$PLATFORM_NAME/ViroKit.framework\"\nfor slice in \"$XCFRAMEWORK\"/*/ViroKit.framework; do\n [ -d \"$slice\" ] || continue\n platform=$(/usr/libexec/PlistBuddy -c 'Print :DTPlatformName' \"$slice/Info.plist\")\n [ \"$platform\" = \"$PLATFORM_NAME\" ] && continue\n mkdir -p \"$STAGE/$platform\"\n cp -R \"$slice\" \"$STAGE/$platform/\"\n set -- \"$@\" -framework \"$STAGE/$platform/ViroKit.framework\"\ndone\nxcodebuild -create-xcframework \"$@\" -output \"$STAGE/ViroKit.xcframework\"\nrm -rf \"$XCFRAMEWORK\"\nmv \"$STAGE/ViroKit.xcframework\" \"$XCFRAMEWORK\"\n\n# Copy over the podspec\ncp \"$PROJECT_DIR/ViroKit.podspec\" \"$VIRO_FRAMEWORK_PATH\"\n"; showEnvVarsInLog = 0; }; B31028429A1A7BF8A56724FA /* [CP] Check Pods Manifest.lock */ = { @@ -5034,7 +5035,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "mkdir -p /var/tmp/build_intermediates/react_viro_kit/ViroRenderer\nVIRO_FRAMEWORK_PATH=/var/tmp/build_intermediates/react_viro_kit/ViroRenderer\nVIRO_DEVICE_FRAMEWORK=$VIRO_FRAMEWORK_PATH/armv7_arm64\nVIRO_SIMULATOR_FRAMEWORK=$VIRO_FRAMEWORK_PATH/x86_64\n\n# Copy the framework (device or simulator) to react-viro\nif [ ${PLATFORM_NAME} = \"iphonesimulator\" ]; then\nmkdir -p $VIRO_SIMULATOR_FRAMEWORK\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_SIMULATOR_FRAMEWORK\nelse\nmkdir -p $VIRO_DEVICE_FRAMEWORK\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_DEVICE_FRAMEWORK\nfi\n\n# If both the device and simulator frameworks now exist, create the Universal framework\nif [ -f $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit ] && [ -f $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit ]; then\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_FRAMEWORK_PATH\nlipo $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit -create -output $VIRO_FRAMEWORK_PATH/ViroKit.framework/ViroKit\nfi\n\n# Copy over the podspec\ncp -r $PROJECT_DIR/ViroKit.podspec $VIRO_FRAMEWORK_PATH\n"; + shellScript = "mkdir -p /var/tmp/build_intermediates/react_viro_kit/ViroRenderer\nVIRO_FRAMEWORK_PATH=/var/tmp/build_intermediates/react_viro_kit/ViroRenderer\nVIRO_DEVICE_FRAMEWORK=$VIRO_FRAMEWORK_PATH/armv7_arm64\nVIRO_SIMULATOR_FRAMEWORK=$VIRO_FRAMEWORK_PATH/x86_64\n\n# Copy the framework (device or simulator) to react-viro\nif [ ${PLATFORM_NAME} = \"iphonesimulator\" ]; then\nmkdir -p $VIRO_SIMULATOR_FRAMEWORK\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_SIMULATOR_FRAMEWORK\nelse\nmkdir -p $VIRO_DEVICE_FRAMEWORK\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_DEVICE_FRAMEWORK\nfi\n\n# If both the device and simulator frameworks now exist, create the Universal framework\n# An arm64 simulator slice cannot share a fat binary with the arm64 device slice, so the universal\n# framework is only made when the two builds have no architecture in common.\nOVERLAP=\nif [ -f $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit ] && [ -f $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit ]; then\nfor ARCH in $(lipo -archs $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit); do\ncase \" $(lipo -archs $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit) \" in *\" $ARCH \"*) OVERLAP=1;; esac\ndone\nfi\nif [ -f $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit ] && [ -f $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit ] && [ -z \"$OVERLAP\" ]; then\ncp -r $TARGET_BUILD_DIR/ViroKit.framework $VIRO_FRAMEWORK_PATH\nlipo $VIRO_SIMULATOR_FRAMEWORK/ViroKit.framework/ViroKit $VIRO_DEVICE_FRAMEWORK/ViroKit.framework/ViroKit -create -output $VIRO_FRAMEWORK_PATH/ViroKit.framework/ViroKit\nfi\n\n# Copy over the podspec\ncp -r $PROJECT_DIR/ViroKit.podspec $VIRO_FRAMEWORK_PATH\n"; showEnvVarsInLog = 0; }; F889831C70414DF33A563C8A /* [CP] Copy Pods Resources */ = { @@ -5928,10 +5929,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries/reactvisioncca/arm64", - ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/freetype/armv7_arm64", @@ -5941,6 +5939,7 @@ "$(PROJECT_DIR)/Libraries/reactvisioncca/arm64", ); "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(PROJECT_DIR)/Libraries/GVRAudioSDK/simulator", "$(inherited)", "$(PROJECT_DIR)/Libraries/freetype/x86_64", "$(PROJECT_DIR)/Libraries/protobuf/x86_64", @@ -6036,7 +6035,6 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/harfbuzz/armv7_arm64", - "$(PROJECT_DIR)/Libraries/reactvisioncca/arm64", ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", @@ -6047,6 +6045,7 @@ "$(PROJECT_DIR)/Libraries/reactvisioncca/arm64", ); "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(PROJECT_DIR)/Libraries/GVRAudioSDK/simulator", "$(inherited)", "$(PROJECT_DIR)/Libraries/freetype/x86_64", "$(PROJECT_DIR)/Libraries/protobuf/x86_64", diff --git a/ios/build_ios_simulator_libs.sh b/ios/build_ios_simulator_libs.sh new file mode 100755 index 000000000..33c9cdfd4 --- /dev/null +++ b/ios/build_ios_simulator_libs.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# build_ios_simulator_libs.sh: adds the arm64 iOS simulator slices ViroKit is missing +# +# ViroKit links six third-party archives. For the simulator it searches the Libraries/*/x86_64 +# folders, which despite the name hold fat simulator archives, and every one of them already had +# an arm64 simulator slice except three: +# +# bullet/x86_64/libLinearMath.a x86_64 only rebuilt from Bullet 2.87 +# protobuf/x86_64/libprotobuf-lite.a x86_64 only rebuilt from protobuf 3.2.0 +# GVRAudioSDK (CocoaPods) no arm64 sim closed source, relabelled by +# retag_for_simulator.py +# +# The versions are pinned to the ones the device archives were built from (version.txt in each +# Libraries folder), so device and simulator run the same code. Compile flags follow the device +# archives too: default symbol visibility, since ViroKit re-exports those symbols on the device. +# +# Apple Silicon simulators run arm64 only (the iOS 27 runtime has no x86_64 at all), so the +# arm64 slice is what makes ViroKit usable there. The existing x86_64 slices are kept. +# +# Output (arm64 simulator slice added in place): +# ios/Libraries/bullet/x86_64/libLinearMath.a +# ios/Libraries/protobuf/x86_64/libprotobuf-lite.a +# ios/Libraries/GVRAudioSDK/simulator/libGVRAudioSDK.a (arm64 + x86_64) +# +# Usage: +# ./build_ios_simulator_libs.sh [bullet3-2.87 source] [protobuf-3.2.0 source] +# Run `pod install` first: the GVR slice is taken from Pods/GVRAudioSDK. + +set -euo pipefail + +BULLET_VERSION="2.87" +PROTOBUF_VERSION="3.2.0" +MIN_VERSION="15.1" +TRIPLE="arm64-apple-ios$MIN_VERSION-simulator" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +LIBS="$SCRIPT_DIR/Libraries" +SYSROOT="$(xcrun --sdk iphonesimulator --show-sdk-path)" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +fetch() { + local url="$1" dir="$2" + echo "--- Downloading $url ---" >&2 + curl -sSL -o "$WORK/src.tar.gz" "$url" + tar xzf "$WORK/src.tar.gz" -C "$WORK" + rm "$WORK/src.tar.gz" + echo "$WORK/$dir" +} + +BT_SRC="${1:-$(fetch "https://github.com/bulletphysics/bullet3/archive/refs/tags/$BULLET_VERSION.tar.gz" "bullet3-$BULLET_VERSION")}" +PB_SRC="${2:-$(fetch "https://github.com/protocolbuffers/protobuf/archive/refs/tags/v$PROTOBUF_VERSION.tar.gz" "protobuf-$PROTOBUF_VERSION")}" +[ -f "$BT_SRC/src/btBulletDynamicsCommon.h" ] || { echo "error: $BT_SRC is not a bullet3 tree" >&2; exit 1; } +[ -f "$PB_SRC/src/google/protobuf/message_lite.h" ] || { echo "error: $PB_SRC is not a protobuf tree" >&2; exit 1; } + +# compile_archive -- +compile_archive() { + local out="$1" root="$2"; shift 2 + local flags=() + while [ "$1" != "--" ]; do flags+=( "$1" ); shift; done + shift + local objdir="$WORK/obj-$(basename "$out" .a)" + mkdir -p "$objdir" + local objects=() + for src in "$@"; do + local obj="$objdir/$(echo "${src#$root/}" | tr '/' '_' | sed 's/\.[a-z]*$/.o/')" + xcrun clang++ -target "$TRIPLE" -isysroot "$SYSROOT" -O2 -fPIC "${flags[@]}" -c "$src" -o "$obj" + objects+=( "$obj" ) + done + xcrun libtool -static -no_warning_for_no_symbols -o "$out" "${objects[@]}" + echo " $(basename "$out"): ${#objects[@]} objects" +} + +# add_slice +add_slice() { + local fat="$1" slice="$2" + if xcrun lipo -archs "$fat" | tr ' ' '\n' | grep -qx arm64; then + xcrun lipo "$fat" -remove arm64 -output "$fat" + fi + xcrun lipo -create "$fat" "$slice" -output "$fat" + echo " $fat: $(xcrun lipo -archs "$fat")" +} + +echo "--- LinearMath (Bullet $BULLET_VERSION) ---" +# Same flags as build_bullet_visionos.sh, which explains each of them, minus -fvisibility=hidden. +compile_archive "$WORK/libLinearMath.a" "$BT_SRC/src" \ + -std=c++11 -I"$BT_SRC/src" \ + -Wno-deprecated-declarations -Wno-unused-parameter -Wno-unused-variable \ + -Wno-unused-but-set-variable -Wno-deprecated-copy -Wno-argument-outside-range -Wno-nontrivial-memcall \ + -- $(find "$BT_SRC/src/LinearMath" -name '*.cpp' | sort) +add_slice "$LIBS/bullet/x86_64/libLinearMath.a" "$WORK/libLinearMath.a" + +echo "--- protobuf-lite $PROTOBUF_VERSION ---" +# The libprotobuf-lite source list from the release's cmake/libprotobuf-lite.cmake. The two x86 +# atomicops files compile to nothing on arm64 and are kept only to match it. +# The renderer's protobuf lives in namespace google_public (Libraries/protobuf/include and +# Nodes.pb.h), so it cannot collide with the copy inside ARCore. Macros do not expand inside an +# #include <...> path, so the rename touches only the code. +PB="$PB_SRC/src/google/protobuf" +compile_archive "$WORK/libprotobuf-lite.a" "$PB_SRC/src" \ + -std=c++11 -DHAVE_PTHREAD -Dgoogle=google_public -I"$PB_SRC/src" \ + -Wno-deprecated-declarations -Wno-unused-parameter -Wno-deprecated-copy \ + -Wno-invalid-noreturn -Wno-unused-const-variable \ + -- "$PB/arena.cc" "$PB/arenastring.cc" "$PB/extension_set.cc" \ + "$PB/generated_message_util.cc" "$PB/io/coded_stream.cc" "$PB/io/zero_copy_stream.cc" \ + "$PB/io/zero_copy_stream_impl_lite.cc" "$PB/message_lite.cc" "$PB/repeated_field.cc" \ + "$PB/stubs/atomicops_internals_x86_gcc.cc" "$PB/stubs/atomicops_internals_x86_msvc.cc" \ + "$PB/stubs/bytestream.cc" "$PB/stubs/common.cc" "$PB/stubs/int128.cc" "$PB/stubs/once.cc" \ + "$PB/stubs/status.cc" "$PB/stubs/statusor.cc" "$PB/stubs/stringpiece.cc" \ + "$PB/stubs/stringprintf.cc" "$PB/stubs/structurally_valid.cc" "$PB/stubs/strutil.cc" \ + "$PB/stubs/time.cc" "$PB/wire_format_lite.cc" +add_slice "$LIBS/protobuf/x86_64/libprotobuf-lite.a" "$WORK/libprotobuf-lite.a" + +echo "--- GVRAudioSDK ---" +GVR="$SCRIPT_DIR/Pods/GVRAudioSDK/Libraries/libGVRAudioSDK.a" +[ -f "$GVR" ] || { echo "error: $GVR not found; run pod install first" >&2; exit 1; } +mkdir -p "$LIBS/GVRAudioSDK/simulator" +xcrun lipo "$GVR" -thin arm64 -output "$WORK/gvr-device-arm64.a" +xcrun lipo "$GVR" -thin x86_64 -output "$WORK/gvr-x86_64.a" +python3 "$SCRIPT_DIR/retag_for_simulator.py" "$WORK/gvr-device-arm64.a" "$WORK/gvr-sim-arm64.a" "$MIN_VERSION" +rm -f "$LIBS/GVRAudioSDK/simulator/libGVRAudioSDK.a" +xcrun lipo -create "$WORK/gvr-sim-arm64.a" "$WORK/gvr-x86_64.a" \ + -output "$LIBS/GVRAudioSDK/simulator/libGVRAudioSDK.a" +echo " libGVRAudioSDK.a: $(xcrun lipo -archs "$LIBS/GVRAudioSDK/simulator/libGVRAudioSDK.a")" diff --git a/ios/retag_for_simulator.py b/ios/retag_for_simulator.py new file mode 100755 index 000000000..797535c16 --- /dev/null +++ b/ios/retag_for_simulator.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Relabel a static archive's arm64 iOS device objects as arm64 iOS simulator objects. + +For closed-source dependencies with no arm64 simulator slice (GVRAudioSDK). arm64 code is the +same instructions on a device and in the simulator; what tells the linker apart is the object's +platform load command, so rewriting that command is enough for the objects to link and run. + +Objects built by older toolchains carry the 16-byte LC_VERSION_MIN_IPHONEOS. Its replacement, +LC_BUILD_VERSION, is 24 bytes, and an object file has no room after its load commands, so every +file offset past them moves. The shift is rounded up to the largest section alignment, keeping +each section's data as aligned in the file as it was before. + +Usage: retag_for_simulator.py [minos, default 15.1] +""" + +import os +import struct +import subprocess +import sys +import tempfile + +LC_SEGMENT_64 = 0x19 +LC_SYMTAB = 0x2 +LC_DYSYMTAB = 0xB +LC_VERSION_MIN_IPHONEOS = 0x25 +LC_BUILD_VERSION = 0x32 +LC_DATA_IN_CODE = 0x29 +LC_LINKER_OPTIMIZATION_HINT = 0x2E +LINKEDIT_DATA_COMMANDS = (LC_DATA_IN_CODE, LC_LINKER_OPTIMIZATION_HINT, 0x26, 0x1E, 0x2B, 0x33, 0x34) +PLATFORM_IOS = 2 +PLATFORM_IOSSIMULATOR = 7 +MH_MAGIC_64 = 0xFEEDFACF +MH_OBJECT = 0x1 +S_ZEROFILL_TYPES = (0x1, 0xC, 0x12) + + +def parse_version(text): + parts = [int(p) for p in text.split(".")] + [0, 0] + return (parts[0] << 16) | (parts[1] << 8) | parts[2] + + +def load_commands(data): + ncmds, sizeofcmds = struct.unpack_from("\n"): + raise ValueError("not an ar archive (thin it with lipo first)") + offset = 8 + while offset < len(data): + header = data[offset:offset + 60] + name = header[:16].decode().rstrip() + size = int(header[48:58].decode()) + body = data[offset + 60:offset + 60 + size] + if name.startswith("#1/"): + name_len = int(name[3:]) + name = body[:name_len].rstrip(b"\0").decode() + body = body[name_len:] + yield name, body + offset += 60 + size + (size & 1) + + +def main(): + source, output = sys.argv[1], sys.argv[2] + minos = parse_version(sys.argv[3] if len(sys.argv) > 3 else "15.1") + with open(source, "rb") as f: + data = f.read() + + with tempfile.TemporaryDirectory() as work: + objects = [] + for index, (name, body) in enumerate(archive_members(data)): + if name.startswith("__.SYMDEF"): + continue + # One directory per member: archives may hold two members with the same name. + member_dir = os.path.join(work, str(index)) + os.mkdir(member_dir) + path = os.path.join(member_dir, name) + with open(path, "wb") as f: + f.write(retag_object(body, minos)) + objects.append(path) + if os.path.exists(output): + os.remove(output) + subprocess.run(["xcrun", "libtool", "-static", "-no_warning_for_no_symbols", + "-o", output] + objects, check=True) + print("%s: %d objects retagged for the arm64 simulator" % (output, len(objects))) + + +if __name__ == "__main__": + main()