From 05eec7e5fbea0a16f6b11f5477c99e70ccdd5d31 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Fri, 26 Sep 2025 14:19:53 -0700 Subject: [PATCH 01/13] Scope Android system V8 search to explicit hints --- .github/jobs/android.yml | 24 +++ .github/jobs/android_tests.yml | 24 +++ .../Android/BabylonNative/build.gradle | 50 +++-- .../ExampleInstrumentedTest.java | 4 +- Apps/Playground/Android/app/build.gradle | 31 ++- .../playground/PlaygroundActivity.java | 4 +- Apps/Playground/Android/gradle.properties | 2 + Install/Test/CMakeLists.txt | 185 +++++++++++++----- azure-pipelines.yml | 2 +- 9 files changed, 249 insertions(+), 77 deletions(-) diff --git a/.github/jobs/android.yml b/.github/jobs/android.yml index c2f9e8f7b..634740b69 100644 --- a/.github/jobs/android.yml +++ b/.github/jobs/android.yml @@ -15,6 +15,30 @@ jobs: parameters: vmImage: ${{ parameters.vmImage }} + - script: | + set -euo pipefail + ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" + if [ -z "$ANDROID_SDK_ROOT" ]; then + echo "ANDROID_SDK_ROOT or ANDROID_HOME must be set" >&2 + exit 1 + fi + + if [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" + elif [ -x "$ANDROID_SDK_ROOT/tools/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/tools/bin/sdkmanager" + else + echo "Unable to locate sdkmanager under $ANDROID_SDK_ROOT" >&2 + exit 1 + fi + + echo "Installing Android NDK version $(NDK_VERSION) using $SDKMANAGER" + yes | "$SDKMANAGER" --licenses + yes | "$SDKMANAGER" --install "ndk;$(NDK_VERSION)" + displayName: 'Install Android NDK $(NDK_VERSION)' + - task: JavaToolInstaller@0 inputs: versionSpec: '17' diff --git a/.github/jobs/android_tests.yml b/.github/jobs/android_tests.yml index c18691022..38fcb4730 100644 --- a/.github/jobs/android_tests.yml +++ b/.github/jobs/android_tests.yml @@ -15,6 +15,30 @@ jobs: parameters: vmImage: ${{ parameters.vmImage }} + - script: | + set -euo pipefail + ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" + if [ -z "$ANDROID_SDK_ROOT" ]; then + echo "ANDROID_SDK_ROOT or ANDROID_HOME must be set" >&2 + exit 1 + fi + + if [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + elif [ -x "$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" + elif [ -x "$ANDROID_SDK_ROOT/tools/bin/sdkmanager" ]; then + SDKMANAGER="$ANDROID_SDK_ROOT/tools/bin/sdkmanager" + else + echo "Unable to locate sdkmanager under $ANDROID_SDK_ROOT" >&2 + exit 1 + fi + + echo "Installing Android NDK version $(NDK_VERSION) using $SDKMANAGER" + yes | "$SDKMANAGER" --licenses + yes | "$SDKMANAGER" --install "ndk;$(NDK_VERSION)" + displayName: 'Install Android NDK $(NDK_VERSION)' + - script: | echo Install Android image echo 'y' | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;android-27;default;x86_64' diff --git a/Apps/Playground/Android/BabylonNative/build.gradle b/Apps/Playground/Android/BabylonNative/build.gradle index 11486d023..daf526bf0 100644 --- a/Apps/Playground/Android/BabylonNative/build.gradle +++ b/Apps/Playground/Android/BabylonNative/build.gradle @@ -22,6 +22,9 @@ if (project.hasProperty("UNITY_BUILD")) { } def arcore_libpath = "${buildDir}/arcore-native" +def deviceAbis = project.hasProperty("ARM64Only") ? ["arm64-v8a"] : ["arm64-v8a", "armeabi-v7a", "x86"] +def xrSimulatorAbis = ["arm64-v8a"] + configurations { natives } android { @@ -31,16 +34,15 @@ android { defaultConfig { minSdk 25 - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" - ndkVersion "23.1.7779620" + ndkVersion "28.2.13676358" if (project.hasProperty("NDK_VERSION")) { def NDKVersion = project.property("NDK_VERSION") ndkVersion "${NDK_VERSION}" } externalNativeBuild { cmake { - abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64" arguments "-DANDROID_STL=c++_shared", "-DENABLE_PCH=OFF", "-DGRAPHICS_API=${graphics_api}", @@ -51,18 +53,39 @@ android { "-DBABYLON_DEBUG_TRACE=ON" } } - ndk { - if (project.hasProperty("ARM64Only")) { - abiFilters "arm64-v8a" - } else { - abiFilters "arm64-v8a", "armeabi-v7a", "x86" - } - } packagingOptions { exclude '**/libarcore_sdk_c.so' } } + flavorDimensions "runtime" + productFlavors { + device { + dimension "runtime" + ndk { + abiFilters.addAll(deviceAbis) + } + externalNativeBuild { + cmake { + abiFilters.clear() + abiFilters.addAll(deviceAbis) + } + } + } + androidXrSimulator { + dimension "runtime" + ndk { + abiFilters.addAll(xrSimulatorAbis) + } + externalNativeBuild { + cmake { + abiFilters.clear() + abiFilters.addAll(xrSimulatorAbis) + } + } + } + } + externalNativeBuild { cmake { version '3.19.6+' @@ -92,10 +115,11 @@ dependencies { // ARCore library implementation 'com.google.ar:core:1.14.0' natives 'com.google.ar:core:1.14.0' - implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'androidx.appcompat:appcompat:1.6.1' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/Apps/Playground/Android/BabylonNative/src/androidTest/java/com/example/babylonnative/ExampleInstrumentedTest.java b/Apps/Playground/Android/BabylonNative/src/androidTest/java/com/example/babylonnative/ExampleInstrumentedTest.java index 1be1dc04b..290346d94 100644 --- a/Apps/Playground/Android/BabylonNative/src/androidTest/java/com/example/babylonnative/ExampleInstrumentedTest.java +++ b/Apps/Playground/Android/BabylonNative/src/androidTest/java/com/example/babylonnative/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ package com.example.babylonnative; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/Apps/Playground/Android/app/build.gradle b/Apps/Playground/Android/app/build.gradle index c2a4dd72e..ed39e3e12 100644 --- a/Apps/Playground/Android/app/build.gradle +++ b/Apps/Playground/Android/app/build.gradle @@ -15,22 +15,41 @@ if (project.hasProperty("UNITY_BUILD")) { } def arcore_libpath = "${buildDir}/arcore-native" +def deviceAbis = project.hasProperty("ARM64Only") ? ["arm64-v8a"] : ["arm64-v8a", "armeabi-v7a", "x86"] +def xrSimulatorAbis = ["arm64-v8a"] + configurations { natives } android { - compileSdkVersion 29 + compileSdk 34 defaultConfig { applicationId "com.android.babylonnative.playground" - minSdkVersion "${platformVersion}" - targetSdkVersion 29 - ndkVersion "23.1.7779620" + minSdk platformVersion + targetSdk 34 + ndkVersion "28.2.13676358" if (project.hasProperty("NDK_VERSION")) { def NDKVersion = project.property("NDK_VERSION") ndkVersion "${NDK_VERSION}" } } + flavorDimensions "runtime" + productFlavors { + device { + dimension "runtime" + ndk { + abiFilters.addAll(deviceAbis) + } + } + androidXrSimulator { + dimension "runtime" + ndk { + abiFilters.addAll(xrSimulatorAbis) + } + } + } + packagingOptions { jniLibs { pickFirsts += ['lib/*/libv8android.so', 'lib/*/libjsc.so', 'lib/*/libBabylonNativeJNI.so', 'lib/*/libc++_shared.so'] @@ -62,8 +81,8 @@ dependencies { implementation project(':BabylonNative') natives 'com.google.ar:core:1.14.0' - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:design:27.1.1' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' } // Extracts the shared libraries from aars in the natives configuration. diff --git a/Apps/Playground/Android/app/src/main/java/com/android/babylonnative/playground/PlaygroundActivity.java b/Apps/Playground/Android/app/src/main/java/com/android/babylonnative/playground/PlaygroundActivity.java index 3064c14b7..0916e6860 100644 --- a/Apps/Playground/Android/app/src/main/java/com/android/babylonnative/playground/PlaygroundActivity.java +++ b/Apps/Playground/Android/app/src/main/java/com/android/babylonnative/playground/PlaygroundActivity.java @@ -4,8 +4,8 @@ import android.app.Activity; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import android.view.View; import com.library.babylonnative.BabylonView; diff --git a/Apps/Playground/Android/gradle.properties b/Apps/Playground/Android/gradle.properties index f6bd6dd09..4ad771bd8 100644 --- a/Apps/Playground/Android/gradle.properties +++ b/Apps/Playground/Android/gradle.properties @@ -12,3 +12,5 @@ org.gradle.jvmargs=-Xmx1536m # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # jsEngine=JavaScriptCore +android.useAndroidX=true +android.enableJetifier=true diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index ae9523e1d..a1bdae568 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -35,9 +35,53 @@ if(MSVC) add_compile_options(/Zc:__cplusplus) endif() +set(USE_SYSTEM_V8 FALSE) +if(ANDROID) + set(_V8_INCLUDE_HINTS) + if(DEFINED ENV{V8_INCLUDE_DIR}) + list(APPEND _V8_INCLUDE_HINTS $ENV{V8_INCLUDE_DIR}) + endif() + if(DEFINED V8_INCLUDE_DIR) + list(APPEND _V8_INCLUDE_HINTS ${V8_INCLUDE_DIR}) + endif() + + find_path(SYSTEM_V8_INCLUDE_DIR + NAMES v8.h + HINTS ${_V8_INCLUDE_HINTS}) + + if(SYSTEM_V8_INCLUDE_DIR) + set(_V8_LIB_HINTS) + if(DEFINED ENV{V8_LIB_DIR}) + list(APPEND _V8_LIB_HINTS $ENV{V8_LIB_DIR}) + endif() + if(DEFINED V8_LIB_DIR) + list(APPEND _V8_LIB_HINTS ${V8_LIB_DIR}) + endif() + + find_library(SYSTEM_V8_MONOLITH_LIB NAMES v8_monolith v8 HINTS ${_V8_LIB_HINTS}) + find_library(SYSTEM_V8_LIBBASE_LIB NAMES v8_libbase HINTS ${_V8_LIB_HINTS}) + find_library(SYSTEM_V8_LIBPLATFORM_LIB NAMES v8_libplatform HINTS ${_V8_LIB_HINTS}) + find_library(SYSTEM_V8_ICUUC_LIB NAMES icuuc HINTS ${_V8_LIB_HINTS}) + find_library(SYSTEM_V8_ICUI18N_LIB NAMES icui18n HINTS ${_V8_LIB_HINTS}) + find_library(SYSTEM_V8_ZLIB_LIB NAMES z zlib HINTS ${_V8_LIB_HINTS}) + + if(SYSTEM_V8_MONOLITH_LIB) + set(USE_SYSTEM_V8 TRUE) + endif() + endif() +endif() + if(NAPI_JAVASCRIPT_ENGINE STREQUAL "") - message("No JS Engine provided. Defaulting to Chakra.") - set(NAPI_JAVASCRIPT_ENGINE "Chakra") + if(APPLE) + message(STATUS "No JS Engine provided. Defaulting to JavaScriptCore on Apple platforms.") + set(NAPI_JAVASCRIPT_ENGINE "JavaScriptCore") + elseif(ANDROID AND USE_SYSTEM_V8) + message(STATUS "No JS Engine provided. Defaulting to system-installed V8 for Android builds.") + set(NAPI_JAVASCRIPT_ENGINE "V8") + else() + message("No JS Engine provided. Defaulting to Chakra.") + set(NAPI_JAVASCRIPT_ENGINE "Chakra") + endif() endif() # executable stub @@ -73,57 +117,89 @@ if(NAPI_JAVASCRIPT_ENGINE STREQUAL "JSI") set(SOURCES ${SOURCES} "${V8JSI_PACKAGE_PATH}/build/native/jsi/jsi/jsi.cpp") set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} "v8jsi.dll.lib") elseif(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8") - set_cpu_platform_arch() - set(V8_VERSION "11.9.169.4") - download_nuget() - set(V8_PACKAGE_PATH "${NUGET_PATH}/packages/v8-v143-${CPU_ARCH}.${V8_VERSION}") - set(V8_PACKAGE_PATH "${NUGET_PATH}/packages/v8-v143-${CPU_ARCH}.${V8_VERSION}") - set(V8_REDIST_PACKAGE_PATH "${NUGET_PATH}/packages/v8.redist-v143-${CPU_ARCH}.${V8_VERSION}") - - add_library(v8_libbase SHARED IMPORTED) - set_target_properties(v8_libbase PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libbase.dll.lib") - add_library(v8_libplatform SHARED IMPORTED) - set_target_properties(v8_libplatform PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libplatform.dll.lib") - add_library(v8 SHARED IMPORTED) - set_target_properties(v8 PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8.dll.lib") - target_link_libraries(v8 INTERFACE v8_libbase INTERFACE v8_libplatform) - target_include_directories(v8 INTERFACE "${V8_PACKAGE_PATH}/include") - - set(V8_DIST - "${V8_REDIST_PACKAGE_PATH}/lib/Release/icudtl.dat" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_icu_icui18n.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_abseil-cpp_absl.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/icuuc.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8_libbase.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8_libplatform.dll" - "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_zlib.dll") - - # only 1 imported location per library -> Adding 1 library per file - foreach(V8FILE ${V8_DIST}) - get_filename_component(V8FILE_NAME "${V8FILE}" NAME_WE) - add_library("v8::${V8FILE_NAME}" SHARED IMPORTED) - set_target_properties("v8::${V8FILE_NAME}" PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libbase.dll.lib") - set_target_properties("v8::${V8FILE_NAME}" PROPERTIES IMPORTED_LOCATION ${V8FILE}) - endforeach() - - set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} - v8 - v8inspector - llhttp - v8::icudtl - v8::third_party_icu_icui18n - v8::icuuc - v8::v8 - v8::v8_libbase - v8::v8_libplatform - v8::third_party_zlib) - - if(CPU_ARCH STREQUAL "x64") - # Enable V8 Pointer Compression - # https://v8.dev/blog/pointer-compression - # https://stackoverflow.com/q/62921373 - set(NAPI_DEFINITIONS PUBLIC V8_COMPRESS_POINTERS) + if(USE_SYSTEM_V8 AND NOT WIN32) + message(STATUS "Linking against system-installed V8 libraries from ${SYSTEM_V8_MONOLITH_LIB}") + set(V8_INCLUDE_DIR ${SYSTEM_V8_INCLUDE_DIR}) + + set(_SYSTEM_V8_LIBS ${SYSTEM_V8_MONOLITH_LIB}) + if(SYSTEM_V8_LIBBASE_LIB) + list(APPEND _SYSTEM_V8_LIBS ${SYSTEM_V8_LIBBASE_LIB}) + endif() + if(SYSTEM_V8_LIBPLATFORM_LIB) + list(APPEND _SYSTEM_V8_LIBS ${SYSTEM_V8_LIBPLATFORM_LIB}) + endif() + if(SYSTEM_V8_ICUUC_LIB) + list(APPEND _SYSTEM_V8_LIBS ${SYSTEM_V8_ICUUC_LIB}) + endif() + if(SYSTEM_V8_ICUI18N_LIB) + list(APPEND _SYSTEM_V8_LIBS ${SYSTEM_V8_ICUI18N_LIB}) + endif() + if(SYSTEM_V8_ZLIB_LIB) + list(APPEND _SYSTEM_V8_LIBS ${SYSTEM_V8_ZLIB_LIB}) + endif() + + find_package(Threads REQUIRED) + if(CMAKE_THREAD_LIBS_INIT) + list(APPEND _SYSTEM_V8_LIBS ${CMAKE_THREAD_LIBS_INIT}) + endif() + if(UNIX AND NOT APPLE) + list(APPEND _SYSTEM_V8_LIBS dl) + endif() + + set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} ${_SYSTEM_V8_LIBS}) + else() + set_cpu_platform_arch() + set(V8_VERSION "11.9.169.4") + download_nuget() + set(V8_PACKAGE_PATH "${NUGET_PATH}/packages/v8-v143-${CPU_ARCH}.${V8_VERSION}") + set(V8_PACKAGE_PATH "${NUGET_PATH}/packages/v8-v143-${CPU_ARCH}.${V8_VERSION}") + set(V8_REDIST_PACKAGE_PATH "${NUGET_PATH}/packages/v8.redist-v143-${CPU_ARCH}.${V8_VERSION}") + + add_library(v8_libbase SHARED IMPORTED) + set_target_properties(v8_libbase PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libbase.dll.lib") + add_library(v8_libplatform SHARED IMPORTED) + set_target_properties(v8_libplatform PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libplatform.dll.lib") + add_library(v8 SHARED IMPORTED) + set_target_properties(v8 PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8.dll.lib") + target_link_libraries(v8 INTERFACE v8_libbase INTERFACE v8_libplatform) + target_include_directories(v8 INTERFACE "${V8_PACKAGE_PATH}/include") + + set(V8_DIST + "${V8_REDIST_PACKAGE_PATH}/lib/Release/icudtl.dat" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_icu_icui18n.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_abseil-cpp_absl.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/icuuc.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8_libbase.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/v8_libplatform.dll" + "${V8_REDIST_PACKAGE_PATH}/lib/Release/third_party_zlib.dll") + + # only 1 imported location per library -> Adding 1 library per file + foreach(V8FILE ${V8_DIST}) + get_filename_component(V8FILE_NAME "${V8FILE}" NAME_WE) + add_library("v8::${V8FILE_NAME}" SHARED IMPORTED) + set_target_properties("v8::${V8FILE_NAME}" PROPERTIES IMPORTED_IMPLIB "${V8_PACKAGE_PATH}/lib/Release/v8_libbase.dll.lib") + set_target_properties("v8::${V8FILE_NAME}" PROPERTIES IMPORTED_LOCATION ${V8FILE}) + endforeach() + + set(ADDITIONAL_LIBRARIES ${ADDITIONAL_LIBRARIES} + v8 + v8inspector + llhttp + v8::icudtl + v8::third_party_icu_icui18n + v8::icuuc + v8::v8 + v8::v8_libbase + v8::v8_libplatform + v8::third_party_zlib) + + if(CPU_ARCH STREQUAL "x64") + # Enable V8 Pointer Compression + # https://v8.dev/blog/pointer-compression + # https://stackoverflow.com/q/62921373 + set(NAPI_DEFINITIONS PUBLIC V8_COMPRESS_POINTERS) + endif() endif() endif() @@ -131,6 +207,9 @@ add_executable(TestInstall ${LOCAL_SCRIPTS} ${NPM_SCRIPTS} ${SOURCES}) set(INSTALL_LIBS_DIR "${BINARY_DIR}/install/lib") target_include_directories(TestInstall PRIVATE "${BINARY_DIR}/install/include") +if(V8_INCLUDE_DIR) + target_include_directories(TestInstall PRIVATE "${V8_INCLUDE_DIR}") +endif() target_link_directories(TestInstall PRIVATE ${INSTALL_LIBS_DIR} ${V8JSI_LIB_PATH_RELEASE}) if(ANDROID OR IOS) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a92d48d65..526d385a2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,7 +8,7 @@ variables: - name: CMAKE_VERSION value: 3.31.6 - name: NDK_VERSION - value: 25.2.9519653 + value: 28.2.13676358 - name: UNITY_BUILD value: true - name: XCODE_VERSION From dda02f62b18d17d3122b9753db4b66d3cb79e80c Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Fri, 26 Sep 2025 15:23:57 -0700 Subject: [PATCH 02/13] Automate NDK license acceptance in CI --- .github/jobs/android.yml | 8 ++++++-- .github/jobs/android_tests.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/jobs/android.yml b/.github/jobs/android.yml index 634740b69..3de909ab4 100644 --- a/.github/jobs/android.yml +++ b/.github/jobs/android.yml @@ -35,8 +35,12 @@ jobs: fi echo "Installing Android NDK version $(NDK_VERSION) using $SDKMANAGER" - yes | "$SDKMANAGER" --licenses - yes | "$SDKMANAGER" --install "ndk;$(NDK_VERSION)" + SDK_ARGS=("--sdk_root=$ANDROID_SDK_ROOT") + + set +o pipefail + yes | "$SDKMANAGER" "${SDK_ARGS[@]}" --licenses >/dev/null + yes | "$SDKMANAGER" "${SDK_ARGS[@]}" --install "ndk;$(NDK_VERSION)" + set -o pipefail displayName: 'Install Android NDK $(NDK_VERSION)' - task: JavaToolInstaller@0 diff --git a/.github/jobs/android_tests.yml b/.github/jobs/android_tests.yml index 38fcb4730..47be0ff74 100644 --- a/.github/jobs/android_tests.yml +++ b/.github/jobs/android_tests.yml @@ -35,8 +35,12 @@ jobs: fi echo "Installing Android NDK version $(NDK_VERSION) using $SDKMANAGER" - yes | "$SDKMANAGER" --licenses - yes | "$SDKMANAGER" --install "ndk;$(NDK_VERSION)" + SDK_ARGS=("--sdk_root=$ANDROID_SDK_ROOT") + + set +o pipefail + yes | "$SDKMANAGER" "${SDK_ARGS[@]}" --licenses >/dev/null + yes | "$SDKMANAGER" "${SDK_ARGS[@]}" --install "ndk;$(NDK_VERSION)" + set -o pipefail displayName: 'Install Android NDK $(NDK_VERSION)' - script: | From 3eded32a2cc24680a24e21ad0cc6f11ba52d60a0 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Fri, 26 Sep 2025 17:35:20 -0700 Subject: [PATCH 03/13] Restore libc++ char traits compatibility for Android --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 624a9c9f2..33216a562 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,13 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(ANDROID) + # Restore legacy std::char_traits specializations removed in newer libc++ versions + # so third-party dependencies that still rely on std::basic_string + # continue to compile when using the Android NDK r28 toolchain. + add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) +endif() + # -------------------------------------------------- # Options # -------------------------------------------------- From ea7e2ebb4a5f866df3a7e9ab0451ae7661732cea Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Fri, 26 Sep 2025 17:51:54 -0700 Subject: [PATCH 04/13] Ensure Android builds restore libc++ char traits --- CMakeLists.txt | 3 ++- Install/Test/CMakeLists.txt | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33216a562..b7393ff6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,11 +78,12 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(ANDROID) +if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") # Restore legacy std::char_traits specializations removed in newer libc++ versions # so third-party dependencies that still rely on std::basic_string # continue to compile when using the Android NDK r28 toolchain. add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") endif() # -------------------------------------------------- diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index a1bdae568..1a32213f0 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -25,6 +25,11 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") + add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") +endif() + if(WIN32 OR (APPLE AND NOT IOS) OR (UNIX AND NOT ANDROID AND NOT APPLE)) FetchContent_MakeAvailable_With_Message(googletest) endif() From b63e021258aa004e6544f6a71b3a4caae70e6bb8 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 00:54:11 -0700 Subject: [PATCH 05/13] Provide Android shim for std::char_traits --- CMakeLists.txt | 7 +- .../Android/std_char_traits_uint16_t.h | 107 ++++++++++++++++++ Install/Test/CMakeLists.txt | 7 +- 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 Dependencies/Android/std_char_traits_uint16_t.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b7393ff6d..5a9c66602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,12 @@ if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") # so third-party dependencies that still rely on std::basic_string # continue to compile when using the Android NDK r28 toolchain. add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") + string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") + + set(_babylon_android_char_traits_header "${CMAKE_SOURCE_DIR}/Dependencies/Android/std_char_traits_uint16_t.h") + if(EXISTS "${_babylon_android_char_traits_header}") + add_compile_options($<$:-include${_babylon_android_char_traits_header}>) + endif() endif() # -------------------------------------------------- diff --git a/Dependencies/Android/std_char_traits_uint16_t.h b/Dependencies/Android/std_char_traits_uint16_t.h new file mode 100644 index 000000000..ff16f690e --- /dev/null +++ b/Dependencies/Android/std_char_traits_uint16_t.h @@ -0,0 +1,107 @@ +#pragma once + +#if defined(__ANDROID__) && defined(_LIBCPP_VERSION) + +// libc++ removed the non-standard std::char_traits specialization in +// C++20. Some transitive Chromium-based dependencies still instantiate +// std::basic_string, so provide a shim that forwards to the supported +// char16_t implementation when we compile against Android's libc++. +#include +#include +#include +#include + +static_assert(sizeof(char16_t) == sizeof(std::uint16_t), + "uint16_t char traits shim assumes char16_t is 16 bits"); + +namespace std +{ + template<> + struct char_traits + { + using char_type = std::uint16_t; + using int_type = char_traits::int_type; + using off_type = char_traits::off_type; + using pos_type = char_traits::pos_type; + using state_type = char_traits::state_type; + + static void assign(char_type& c1, const char_type& c2) noexcept + { + c1 = c2; + } + + static constexpr bool eq(char_type c1, char_type c2) noexcept + { + return c1 == c2; + } + + static constexpr bool lt(char_type c1, char_type c2) noexcept + { + return c1 < c2; + } + + static void assign(char_type* s, std::size_t n, char_type c) + { + char_traits::assign(reinterpret_cast(s), n, static_cast(c)); + } + + static char_type* move(char_type* dst, const char_type* src, std::size_t n) + { + return reinterpret_cast(char_traits::move( + reinterpret_cast(dst), + reinterpret_cast(src), + n)); + } + + static char_type* copy(char_type* dst, const char_type* src, std::size_t n) + { + return reinterpret_cast(char_traits::copy( + reinterpret_cast(dst), + reinterpret_cast(src), + n)); + } + + static int compare(const char_type* s1, const char_type* s2, std::size_t n) + { + return char_traits::compare( + reinterpret_cast(s1), + reinterpret_cast(s2), + n); + } + + static std::size_t length(const char_type* s) + { + return char_traits::length(reinterpret_cast(s)); + } + + static const char_type* find(const char_type* s, std::size_t n, const char_type& a) + { + return reinterpret_cast(char_traits::find( + reinterpret_cast(s), + n, + static_cast(a))); + } + + static constexpr int_type eof() noexcept + { + return char_traits::eof(); + } + + static constexpr int_type to_int_type(char_type c) noexcept + { + return char_traits::to_int_type(static_cast(c)); + } + + static constexpr char_type to_char_type(int_type c) noexcept + { + return static_cast(char_traits::to_char_type(c)); + } + + static constexpr int_type not_eof(int_type c) noexcept + { + return char_traits::not_eof(c); + } + }; +} + +#endif // defined(__ANDROID__) && defined(_LIBCPP_VERSION) diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index 1a32213f0..11e0d2764 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -27,7 +27,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") + string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") + + set(_babylon_android_char_traits_header "${CMAKE_SOURCE_DIR}/Dependencies/Android/std_char_traits_uint16_t.h") + if(EXISTS "${_babylon_android_char_traits_header}") + add_compile_options($<$:-include${_babylon_android_char_traits_header}>) + endif() endif() if(WIN32 OR (APPLE AND NOT IOS) OR (UNIX AND NOT ANDROID AND NOT APPLE)) From 277c3570cc322e09df93b980a037a107effabfcb Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 00:54:17 -0700 Subject: [PATCH 06/13] Enable libc++ legacy char traits via flag --- CMakeLists.txt | 11 +- .../Android/std_char_traits_uint16_t.h | 107 ------------------ Install/Test/CMakeLists.txt | 5 - 3 files changed, 3 insertions(+), 120 deletions(-) delete mode 100644 Dependencies/Android/std_char_traits_uint16_t.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a9c66602..b8a801bc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,16 +79,11 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") - # Restore legacy std::char_traits specializations removed in newer libc++ versions - # so third-party dependencies that still rely on std::basic_string - # continue to compile when using the Android NDK r28 toolchain. + # Restore the legacy std::char_traits specializations removed in newer libc++ versions + # so Chromium-derived dependencies that still rely on std::basic_string + # continue to build with the Android NDK r28 toolchain without requiring header overrides. add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") - - set(_babylon_android_char_traits_header "${CMAKE_SOURCE_DIR}/Dependencies/Android/std_char_traits_uint16_t.h") - if(EXISTS "${_babylon_android_char_traits_header}") - add_compile_options($<$:-include${_babylon_android_char_traits_header}>) - endif() endif() # -------------------------------------------------- diff --git a/Dependencies/Android/std_char_traits_uint16_t.h b/Dependencies/Android/std_char_traits_uint16_t.h deleted file mode 100644 index ff16f690e..000000000 --- a/Dependencies/Android/std_char_traits_uint16_t.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#if defined(__ANDROID__) && defined(_LIBCPP_VERSION) - -// libc++ removed the non-standard std::char_traits specialization in -// C++20. Some transitive Chromium-based dependencies still instantiate -// std::basic_string, so provide a shim that forwards to the supported -// char16_t implementation when we compile against Android's libc++. -#include -#include -#include -#include - -static_assert(sizeof(char16_t) == sizeof(std::uint16_t), - "uint16_t char traits shim assumes char16_t is 16 bits"); - -namespace std -{ - template<> - struct char_traits - { - using char_type = std::uint16_t; - using int_type = char_traits::int_type; - using off_type = char_traits::off_type; - using pos_type = char_traits::pos_type; - using state_type = char_traits::state_type; - - static void assign(char_type& c1, const char_type& c2) noexcept - { - c1 = c2; - } - - static constexpr bool eq(char_type c1, char_type c2) noexcept - { - return c1 == c2; - } - - static constexpr bool lt(char_type c1, char_type c2) noexcept - { - return c1 < c2; - } - - static void assign(char_type* s, std::size_t n, char_type c) - { - char_traits::assign(reinterpret_cast(s), n, static_cast(c)); - } - - static char_type* move(char_type* dst, const char_type* src, std::size_t n) - { - return reinterpret_cast(char_traits::move( - reinterpret_cast(dst), - reinterpret_cast(src), - n)); - } - - static char_type* copy(char_type* dst, const char_type* src, std::size_t n) - { - return reinterpret_cast(char_traits::copy( - reinterpret_cast(dst), - reinterpret_cast(src), - n)); - } - - static int compare(const char_type* s1, const char_type* s2, std::size_t n) - { - return char_traits::compare( - reinterpret_cast(s1), - reinterpret_cast(s2), - n); - } - - static std::size_t length(const char_type* s) - { - return char_traits::length(reinterpret_cast(s)); - } - - static const char_type* find(const char_type* s, std::size_t n, const char_type& a) - { - return reinterpret_cast(char_traits::find( - reinterpret_cast(s), - n, - static_cast(a))); - } - - static constexpr int_type eof() noexcept - { - return char_traits::eof(); - } - - static constexpr int_type to_int_type(char_type c) noexcept - { - return char_traits::to_int_type(static_cast(c)); - } - - static constexpr char_type to_char_type(int_type c) noexcept - { - return static_cast(char_traits::to_char_type(c)); - } - - static constexpr int_type not_eof(int_type c) noexcept - { - return char_traits::not_eof(c); - } - }; -} - -#endif // defined(__ANDROID__) && defined(_LIBCPP_VERSION) diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index 11e0d2764..abdb5cf7b 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -28,11 +28,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") - - set(_babylon_android_char_traits_header "${CMAKE_SOURCE_DIR}/Dependencies/Android/std_char_traits_uint16_t.h") - if(EXISTS "${_babylon_android_char_traits_header}") - add_compile_options($<$:-include${_babylon_android_char_traits_header}>) - endif() endif() if(WIN32 OR (APPLE AND NOT IOS) OR (UNIX AND NOT ANDROID AND NOT APPLE)) From ca622fdad69974e4797325b921ef686ef45e3ba1 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 11:53:35 -0700 Subject: [PATCH 07/13] Update JsRuntimeHost for NDK 28 compatibility --- CMakeLists.txt | 5 +- .../V8Inspector/Source/V8InspectorAgent.cpp | 672 ++++++++++++++++++ 2 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b8a801bc1..b4bbb4486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,10 @@ FetchContent_Declare(ios-cmake GIT_TAG 4.5.0) FetchContent_Declare(JsRuntimeHost GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git - GIT_TAG ef57990dc5533990b2bb194978a12f8c8d83565c) + GIT_TAG c726f284ab906f2f535ac17c3c49cc18c0ea5267 + PATCH_COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp + /Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp) FetchContent_Declare(SPIRV-Cross GIT_REPOSITORY https://github.com/BabylonJS/SPIRV-Cross.git GIT_TAG 6abfcf066d171e9ade7604d91381ebebe4209edc) diff --git a/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp b/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp new file mode 100644 index 000000000..9874729d8 --- /dev/null +++ b/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp @@ -0,0 +1,672 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// This code is based on the old node inspector implementation. See NOTICE.md for Node.js' project license details +#include +#include "V8InspectorSocketServer.h" +#include "V8InspectorUtils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Babylon +{ + const char TAG_CONNECT[] = "#connect"; + const char TAG_DISCONNECT[] = "#disconnect"; + + std::string GetProcessTitle() + { + return "BabylonNative"; + } + + class V8NodeInspector; + + class AgentImpl + { + public: + explicit AgentImpl( + v8::Platform& platform, + v8::Isolate* isolate, + v8::Local context, + const char* context_name); + ~AgentImpl(); + + void Start(const unsigned short port, const std::string& appName); + void Stop(); + + void WaitForDebugger(); + + bool IsStarted(); + bool IsConnected(); + + void PostIncomingMessage(int session_id, const std::string& message); + + private: + using MessageQueue = + std::vector>>; + enum class State + { + kNew, + kAccepting, + kConnected, + kDone, + kError + }; + + void DispatchMessages(); + void Write( + int session_id, + std::unique_ptr message); + bool AppendMessage( + MessageQueue* vector, + int session_id, + std::unique_ptr buffer); + void SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2); + void WaitForFrontendMessage(); + void NotifyMessageReceived(); + + std::mutex incoming_message_cond_m_; + std::condition_variable incoming_message_cond_; + + std::mutex state_m; + + unsigned short port_; + + bool waiting_for_frontend_ = true; + + std::unique_ptr inspector_; + v8::Isolate* isolate_; + MessageQueue incoming_message_queue_; + MessageQueue outgoing_message_queue_; + bool dispatching_messages_; + int session_id_; + std::unique_ptr server_; + + std::string script_name_; + + v8::Platform& platform_; + + friend class ChannelImpl; + friend class DispatchOnInspectorBackendTask; + friend class SetConnectedTask; + friend class V8NodeInspector; + friend void InterruptCallback(v8::Isolate*, void* agent); + + public: + }; + + void InterruptCallback(v8::Isolate*, void* agent) + { + static_cast(agent)->DispatchMessages(); + } + + class DispatchOnInspectorBackendTask : public v8::Task + { + public: + explicit DispatchOnInspectorBackendTask(AgentImpl& agent) + : agent_(agent) + { + } + + private: + // ------------------ + // v8::Task overrides + // ------------------ + + void Run() override + { + agent_.DispatchMessages(); + } + + private: + AgentImpl& agent_; + }; + + class ChannelImpl final : public v8_inspector::V8Inspector::Channel + { + public: + explicit ChannelImpl(AgentImpl& agent) + : agent_(agent) + { + } + virtual ~ChannelImpl() {} + + private: + // -------------------------------------------- + // v8_inspector::V8Inspector::Channel overrides + // -------------------------------------------- + + void sendResponse( + int /*callId*/, + std::unique_ptr message) override + { + agent_.Write(agent_.session_id_, std::move(message)); + } + + void sendNotification( + std::unique_ptr message) override + { + agent_.Write(agent_.session_id_, std::move(message)); + } + + void flushProtocolNotifications() override {} + + private: + AgentImpl& agent_; + }; + + using V8Inspector = v8_inspector::V8Inspector; + + class V8NodeInspector : public v8_inspector::V8InspectorClient + { + public: + V8NodeInspector(AgentImpl& agent) + : agent_(agent) + , waiting_for_resume_(false) + , running_nested_loop_(false) + , inspector_(V8Inspector::create(agent.isolate_, this)) + { + } + + void SetupContext( + v8::Local context, + const char* context_name /*must be null terminated*/) + { + std::unique_ptr name_buffer = utils::Utf8ToStringView(context_name); + v8_inspector::V8ContextInfo info(context, 1, name_buffer->string()); + + std::unique_ptr aux_data_buffer = utils::Utf8ToStringView("{\"isDefault\":true}"); + info.auxData = aux_data_buffer->string(); + + inspector_->contextCreated(info); + } + + void ConnectFrontend() + { + session_ = inspector_->connect( + 1, new ChannelImpl(agent_), v8_inspector::StringView() +// v8-android package and v8 nuget do not share the same V8 version. A change in V8_inspector API forces us to add this +// ifndef check. This will be fixed in a future nuget package update. +#ifndef ANDROID + , v8_inspector::V8Inspector::kFullyTrusted +#endif + ); + } + + void DisconnectFrontend() + { + session_.reset(); + } + + void DispatchMessageFromFrontend(const v8_inspector::StringView& message) + { + std::string messagestr = utils::StringViewToUtf8(message); + + if (agent_.waiting_for_frontend_) + agent_.waiting_for_frontend_ = + messagestr.find("Runtime.runIfWaitingForDebugger") != + std::string::npos; + + session_->dispatchProtocolMessage(message); + } + + V8Inspector* Inspector() + { + return inspector_.get(); + } + + bool IsWaitingForResume() + { + return waiting_for_resume_; + } + + void SchedulePauseOnNextStatement(const v8_inspector::StringView& reason, const v8_inspector::StringView& details) + { + session_->schedulePauseOnNextStatement(reason, details); + } + + private: + // ----------------------------------------- + // v8_inspector::V8InspectorClient overrides + // ----------------------------------------- + + void runMessageLoopOnPause(int /*context_group_id*/) override + { + waiting_for_resume_ = true; + if (running_nested_loop_) + return; + running_nested_loop_ = true; + while (waiting_for_resume_) + { + agent_.WaitForFrontendMessage(); + agent_.DispatchMessages(); + } + waiting_for_resume_ = false; + running_nested_loop_ = false; + } + + double currentTimeMS() override + { + auto duration = std::chrono::system_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(duration) + .count()); + } + + void quitMessageLoopOnPause() override + { + waiting_for_resume_ = false; + } + + v8::Local ensureDefaultContextInGroup( + int /*contextGroupId*/) override + { + return v8::Isolate::GetCurrent()->GetCurrentContext(); + } + + private: + AgentImpl& agent_; + std::atomic waiting_for_resume_{false}; + bool running_nested_loop_; + std::unique_ptr inspector_; + std::unique_ptr session_; + }; + + AgentImpl::AgentImpl( + v8::Platform& platform, + v8::Isolate* isolate, + v8::Local context, + const char* context_name) + : inspector_(nullptr) + , isolate_(isolate) + , dispatching_messages_(false) + , session_id_(0) + , server_(nullptr) + , platform_(platform) + { + inspector_ = std::make_unique(*this); + inspector_->SetupContext(context, context_name); + } + + AgentImpl::~AgentImpl() {} + + void InspectorConsoleCall(const v8::FunctionCallbackInfo& info) + { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local args = info.Data().As(); + CHECK_EQ(args->Length(), 3); + + v8::Local inspector_method = + args->Get(context, 0).ToLocalChecked(); + CHECK(inspector_method->IsFunction()); + v8::Local node_method = args->Get(context, 1).ToLocalChecked(); + CHECK(node_method->IsFunction()); + v8::Local config_value = args->Get(context, 2).ToLocalChecked(); + CHECK(config_value->IsObject()); + v8::Local config_object = config_value.As(); + + std::vector> call_args(info.Length()); + for (int i = 0; i < info.Length(); ++i) + { + call_args[i] = info[i]; + } + + v8::Local in_call_key = utils::OneByteString(isolate, "in_call"); + bool in_call = config_object->Has(context, in_call_key).FromMaybe(false); + if (!in_call) + { + CHECK( + config_object->Set(context, in_call_key, v8::True(isolate)).FromJust()); + CHECK( + !inspector_method.As() + ->Call(context, info.Holder(), static_cast(call_args.size()), call_args.data()) + .IsEmpty()); + } + + v8::TryCatch try_catch(info.GetIsolate()); + static_cast(node_method.As()->Call( + context, info.Holder(), static_cast(call_args.size()), call_args.data())); + CHECK(config_object->Delete(context, in_call_key).FromJust()); + if (try_catch.HasCaught()) + try_catch.ReThrow(); + } + + void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) + { + + v8::Local array = + v8::Array::New(v8::Isolate::GetCurrent(), args.Length()); + CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 0, args[0]) + .FromJust()); + CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 1, args[1]) + .FromJust()); + CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 2, args[2]) + .FromJust()); + args.GetReturnValue().Set(v8::Function::New( + v8::Isolate::GetCurrent()->GetCurrentContext(), + InspectorConsoleCall, + array).ToLocalChecked()); + } + + void AgentImpl::Start(const unsigned short port, const std::string& appName) + { + port_ = port; + script_name_ = appName; + + // be sure server_ is not still in use here or its allocation will be replace in the thread func + // this can happen if reusing the same AgentImpl object, stopping and restarting before the InspectorSocketServer is properly pulled down + if (server_) { + throw std::runtime_error("can't start again the server as previous InspectorSocketServer is still active."); + } + + auto delegate = std::make_unique(*this, "", script_name_, false); + server_ = std::make_unique(std::move(delegate), port_); + server_->Start(); + } + + void AgentImpl::WaitForDebugger() + { + WaitForFrontendMessage(); + + while (waiting_for_frontend_) + DispatchMessages(); + + std::string reasonstr("Break on start"); + v8_inspector::StringView reason( + reinterpret_cast(reasonstr.c_str()), reasonstr.size()), + details( + reinterpret_cast(reasonstr.c_str()), + reasonstr.size()); + inspector_->SchedulePauseOnNextStatement(reason, details); + } + + void AgentImpl::Stop() + { + if (inspector_) + { + inspector_->DisconnectFrontend(); + inspector_.reset(); + } + + if (server_) + { + server_->Stop(); + } + } + + bool AgentImpl::IsConnected() + { + return server_ && server_->IsConnected(); + } + + bool AgentImpl::IsStarted() + { + return !!server_; + } + + std::unique_ptr ToProtocolString( + v8::Local value) + { + if (value.IsEmpty() || value->IsNull() || value->IsUndefined() || + !value->IsString()) + { + return v8_inspector::StringBuffer::create(v8_inspector::StringView()); + } + v8::Local string_value = v8::Local::Cast(value); + int len = string_value->Length(); + std::vector buffer(len, 0); + string_value->Write(v8::Isolate::GetCurrent(), buffer.data(), 0, len); + return v8_inspector::StringBuffer::create( + v8_inspector::StringView(buffer.data(), len)); + } + + bool AgentImpl::AppendMessage( + MessageQueue* queue, + int session_id, + std::unique_ptr buffer) + { + std::unique_lock lock(state_m); + bool trigger_pumping = queue->empty(); + queue->push_back(std::make_pair(session_id, std::move(buffer))); + return trigger_pumping; + } + + void AgentImpl::SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2) + { + std::unique_lock lock(state_m); + vector1->swap(*vector2); + } + + void AgentImpl::PostIncomingMessage( + int session_id, + const std::string& message) + { + if (AppendMessage(&incoming_message_queue_, session_id, utils::Utf8ToStringView(message))) + { + std::shared_ptr foregroundTaskRunner; + +#ifdef USE_DEFAULT_PLATFORM + // Need to get the foreground runner from the isolate data slot + v8runtime::IsolateData* isolate_data = reinterpret_cast(isolate_->GetData(v8runtime::ISOLATE_DATA_SLOT)); + foregroundTaskRunner = isolate_data->foreground_task_runner_; +#else + foregroundTaskRunner = platform_.GetForegroundTaskRunner(isolate_); +#endif + foregroundTaskRunner->PostTask(std::make_unique(*this)); + isolate_->RequestInterrupt(InterruptCallback, this); + } + NotifyMessageReceived(); + } + + void AgentImpl::WaitForFrontendMessage() + { + std::unique_lock lock(incoming_message_cond_m_); + if (incoming_message_queue_.empty()) + incoming_message_cond_.wait( + lock, [this] { return !incoming_message_queue_.empty(); }); + } + + void AgentImpl::NotifyMessageReceived() + { + incoming_message_cond_.notify_all(); + } + + void AgentImpl::DispatchMessages() + { + // This function can be reentered if there was an incoming message while + // V8 was processing another inspector request (e.g. if the user is + // evaluating a long-running JS code snippet). This can happen only at + // specific points (e.g. the lines that call inspector_ methods) + if (dispatching_messages_) + return; + dispatching_messages_ = true; + MessageQueue tasks; + do + { + tasks.clear(); + SwapBehindLock(&incoming_message_queue_, &tasks); + for (const MessageQueue::value_type& pair : tasks) + { + v8_inspector::StringView message = pair.second->string(); + std::string tag; + if (message.length() == sizeof(TAG_CONNECT) - 1 || + message.length() == sizeof(TAG_DISCONNECT) - 1) + { + tag = utils::StringViewToUtf8(message); + } + + if (tag == TAG_CONNECT) + { + session_id_ = pair.first; + inspector_->ConnectFrontend(); + } + else if (tag == TAG_DISCONNECT) + { + inspector_->DisconnectFrontend(); + } + else if (inspector_) + { + inspector_->DispatchMessageFromFrontend(message); + } + else + { +#ifdef WIN32 + OutputDebugStringW(L"Warning: V8 inspector message dropped - "); + if (message.is8Bit()) + { + OutputDebugStringA(reinterpret_cast(message.characters8())); + } + else + { + OutputDebugStringW(reinterpret_cast(message.characters16())); + } + OutputDebugStringW(L"\n"); +#endif + } + } + } while (!tasks.empty()); + dispatching_messages_ = false; + } + + void AgentImpl::Write( + int session_id, + std::unique_ptr inspector_message) + { + AppendMessage( + &outgoing_message_queue_, session_id, std::move(inspector_message)); + + MessageQueue outgoing_messages; + SwapBehindLock(&outgoing_message_queue_, &outgoing_messages); + for (const MessageQueue::value_type& outgoing : outgoing_messages) + { + v8_inspector::StringView view = outgoing.second->string(); + assert(server_); + if (server_) + { + if (view.length() == 0) + { + server_->Stop(); + } + else + { + server_->Send( + outgoing.first, utils::StringViewToUtf8(outgoing.second->string())); + } + } + } + } + + // Exported class Agent + V8InspectorAgent::V8InspectorAgent( + v8::Platform& platform, + v8::Isolate* isolate, + v8::Local context, + const char* context_name) + : impl(std::make_unique(platform, isolate, context, context_name)) + { + } + + V8InspectorAgent::~V8InspectorAgent() + { + } + + void V8InspectorAgent::WaitForDebugger() + { + impl->WaitForDebugger(); + } + + void V8InspectorAgent::Stop() + { + impl->Stop(); + } + + void V8InspectorAgent::Start(const unsigned short port, const std::string& appName) + { + impl->Start(port, appName); + } + + bool V8InspectorAgent::IsStarted() + { + return impl->IsStarted(); + } + + bool V8InspectorAgent::IsConnected() + { + return impl->IsConnected(); + } + + InspectorAgentDelegate::InspectorAgentDelegate( + AgentImpl& agent, + const std::string& script_path, + const std::string& script_name, + bool wait) + : agent_(agent) + , connected_(false) + , script_name_(script_name) + , script_path_(script_path) + , target_id_(utils::GenerateUniqueID()) + , waiting_(wait) + { + } + + void InspectorAgentDelegate::StartSession( + int session_id, + const std::string& /*target_id*/) + { + connected_ = true; + agent_.PostIncomingMessage(session_id, TAG_CONNECT); + } + + void InspectorAgentDelegate::MessageReceived( + int session_id, + const std::string& message) + { + // TODO(pfeldman): Instead of blocking execution while debugger + // engages, node should wait for the run callback from the remote client + // and initiate its startup. This is a change to node.cc that should be + // upstreamed separately. + if (waiting_) + { + if (message.find("\"Runtime.runIfWaitingForDebugger\"") != + std::string::npos) + { + waiting_ = false; + } + } + agent_.PostIncomingMessage(session_id, message); + } + + void InspectorAgentDelegate::EndSession(int session_id) + { + connected_ = false; + agent_.PostIncomingMessage(session_id, TAG_DISCONNECT); + } + + std::vector InspectorAgentDelegate::GetTargetIds() + { + return {target_id_}; + } + + std::string InspectorAgentDelegate::GetTargetTitle(const std::string& /*id*/) + { + return script_name_.empty() ? GetProcessTitle() : script_name_; + } + + std::string InspectorAgentDelegate::GetTargetUrl(const std::string& /*id*/) + { + return "file://" + script_path_; + } + +} // namespace inspector \ No newline at end of file From 4758977cc75605e88382bfb2df5beeb1ed952507 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 11:53:39 -0700 Subject: [PATCH 08/13] Apply JsRuntimeHost V8 inspector fix via patch --- CMakeLists.txt | 7 +- .../V8Inspector/Source/V8InspectorAgent.cpp | 672 ------------------ .../Patches/JsRuntimeHost/apply_patch.cmake | 36 + .../v8inspector-utf16-vector.patch | 13 + 4 files changed, 53 insertions(+), 675 deletions(-) delete mode 100644 Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp create mode 100644 Dependencies/Patches/JsRuntimeHost/apply_patch.cmake create mode 100644 Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index b4bbb4486..67b1c40f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,9 +38,10 @@ FetchContent_Declare(ios-cmake FetchContent_Declare(JsRuntimeHost GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git GIT_TAG c726f284ab906f2f535ac17c3c49cc18c0ea5267 - PATCH_COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp - /Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp) + PATCH_COMMAND ${CMAKE_COMMAND} + -Dpatch_file=${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch + -Dsource_dir= + -P ${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake) FetchContent_Declare(SPIRV-Cross GIT_REPOSITORY https://github.com/BabylonJS/SPIRV-Cross.git GIT_TAG 6abfcf066d171e9ade7604d91381ebebe4209edc) diff --git a/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp b/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp deleted file mode 100644 index 9874729d8..000000000 --- a/Dependencies/Patches/JsRuntimeHost/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// This code is based on the old node inspector implementation. See NOTICE.md for Node.js' project license details -#include -#include "V8InspectorSocketServer.h" -#include "V8InspectorUtils.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Babylon -{ - const char TAG_CONNECT[] = "#connect"; - const char TAG_DISCONNECT[] = "#disconnect"; - - std::string GetProcessTitle() - { - return "BabylonNative"; - } - - class V8NodeInspector; - - class AgentImpl - { - public: - explicit AgentImpl( - v8::Platform& platform, - v8::Isolate* isolate, - v8::Local context, - const char* context_name); - ~AgentImpl(); - - void Start(const unsigned short port, const std::string& appName); - void Stop(); - - void WaitForDebugger(); - - bool IsStarted(); - bool IsConnected(); - - void PostIncomingMessage(int session_id, const std::string& message); - - private: - using MessageQueue = - std::vector>>; - enum class State - { - kNew, - kAccepting, - kConnected, - kDone, - kError - }; - - void DispatchMessages(); - void Write( - int session_id, - std::unique_ptr message); - bool AppendMessage( - MessageQueue* vector, - int session_id, - std::unique_ptr buffer); - void SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2); - void WaitForFrontendMessage(); - void NotifyMessageReceived(); - - std::mutex incoming_message_cond_m_; - std::condition_variable incoming_message_cond_; - - std::mutex state_m; - - unsigned short port_; - - bool waiting_for_frontend_ = true; - - std::unique_ptr inspector_; - v8::Isolate* isolate_; - MessageQueue incoming_message_queue_; - MessageQueue outgoing_message_queue_; - bool dispatching_messages_; - int session_id_; - std::unique_ptr server_; - - std::string script_name_; - - v8::Platform& platform_; - - friend class ChannelImpl; - friend class DispatchOnInspectorBackendTask; - friend class SetConnectedTask; - friend class V8NodeInspector; - friend void InterruptCallback(v8::Isolate*, void* agent); - - public: - }; - - void InterruptCallback(v8::Isolate*, void* agent) - { - static_cast(agent)->DispatchMessages(); - } - - class DispatchOnInspectorBackendTask : public v8::Task - { - public: - explicit DispatchOnInspectorBackendTask(AgentImpl& agent) - : agent_(agent) - { - } - - private: - // ------------------ - // v8::Task overrides - // ------------------ - - void Run() override - { - agent_.DispatchMessages(); - } - - private: - AgentImpl& agent_; - }; - - class ChannelImpl final : public v8_inspector::V8Inspector::Channel - { - public: - explicit ChannelImpl(AgentImpl& agent) - : agent_(agent) - { - } - virtual ~ChannelImpl() {} - - private: - // -------------------------------------------- - // v8_inspector::V8Inspector::Channel overrides - // -------------------------------------------- - - void sendResponse( - int /*callId*/, - std::unique_ptr message) override - { - agent_.Write(agent_.session_id_, std::move(message)); - } - - void sendNotification( - std::unique_ptr message) override - { - agent_.Write(agent_.session_id_, std::move(message)); - } - - void flushProtocolNotifications() override {} - - private: - AgentImpl& agent_; - }; - - using V8Inspector = v8_inspector::V8Inspector; - - class V8NodeInspector : public v8_inspector::V8InspectorClient - { - public: - V8NodeInspector(AgentImpl& agent) - : agent_(agent) - , waiting_for_resume_(false) - , running_nested_loop_(false) - , inspector_(V8Inspector::create(agent.isolate_, this)) - { - } - - void SetupContext( - v8::Local context, - const char* context_name /*must be null terminated*/) - { - std::unique_ptr name_buffer = utils::Utf8ToStringView(context_name); - v8_inspector::V8ContextInfo info(context, 1, name_buffer->string()); - - std::unique_ptr aux_data_buffer = utils::Utf8ToStringView("{\"isDefault\":true}"); - info.auxData = aux_data_buffer->string(); - - inspector_->contextCreated(info); - } - - void ConnectFrontend() - { - session_ = inspector_->connect( - 1, new ChannelImpl(agent_), v8_inspector::StringView() -// v8-android package and v8 nuget do not share the same V8 version. A change in V8_inspector API forces us to add this -// ifndef check. This will be fixed in a future nuget package update. -#ifndef ANDROID - , v8_inspector::V8Inspector::kFullyTrusted -#endif - ); - } - - void DisconnectFrontend() - { - session_.reset(); - } - - void DispatchMessageFromFrontend(const v8_inspector::StringView& message) - { - std::string messagestr = utils::StringViewToUtf8(message); - - if (agent_.waiting_for_frontend_) - agent_.waiting_for_frontend_ = - messagestr.find("Runtime.runIfWaitingForDebugger") != - std::string::npos; - - session_->dispatchProtocolMessage(message); - } - - V8Inspector* Inspector() - { - return inspector_.get(); - } - - bool IsWaitingForResume() - { - return waiting_for_resume_; - } - - void SchedulePauseOnNextStatement(const v8_inspector::StringView& reason, const v8_inspector::StringView& details) - { - session_->schedulePauseOnNextStatement(reason, details); - } - - private: - // ----------------------------------------- - // v8_inspector::V8InspectorClient overrides - // ----------------------------------------- - - void runMessageLoopOnPause(int /*context_group_id*/) override - { - waiting_for_resume_ = true; - if (running_nested_loop_) - return; - running_nested_loop_ = true; - while (waiting_for_resume_) - { - agent_.WaitForFrontendMessage(); - agent_.DispatchMessages(); - } - waiting_for_resume_ = false; - running_nested_loop_ = false; - } - - double currentTimeMS() override - { - auto duration = std::chrono::system_clock::now().time_since_epoch(); - return static_cast(std::chrono::duration_cast(duration) - .count()); - } - - void quitMessageLoopOnPause() override - { - waiting_for_resume_ = false; - } - - v8::Local ensureDefaultContextInGroup( - int /*contextGroupId*/) override - { - return v8::Isolate::GetCurrent()->GetCurrentContext(); - } - - private: - AgentImpl& agent_; - std::atomic waiting_for_resume_{false}; - bool running_nested_loop_; - std::unique_ptr inspector_; - std::unique_ptr session_; - }; - - AgentImpl::AgentImpl( - v8::Platform& platform, - v8::Isolate* isolate, - v8::Local context, - const char* context_name) - : inspector_(nullptr) - , isolate_(isolate) - , dispatching_messages_(false) - , session_id_(0) - , server_(nullptr) - , platform_(platform) - { - inspector_ = std::make_unique(*this); - inspector_->SetupContext(context, context_name); - } - - AgentImpl::~AgentImpl() {} - - void InspectorConsoleCall(const v8::FunctionCallbackInfo& info) - { - v8::Isolate* isolate = info.GetIsolate(); - v8::Local context = isolate->GetCurrentContext(); - - v8::Local args = info.Data().As(); - CHECK_EQ(args->Length(), 3); - - v8::Local inspector_method = - args->Get(context, 0).ToLocalChecked(); - CHECK(inspector_method->IsFunction()); - v8::Local node_method = args->Get(context, 1).ToLocalChecked(); - CHECK(node_method->IsFunction()); - v8::Local config_value = args->Get(context, 2).ToLocalChecked(); - CHECK(config_value->IsObject()); - v8::Local config_object = config_value.As(); - - std::vector> call_args(info.Length()); - for (int i = 0; i < info.Length(); ++i) - { - call_args[i] = info[i]; - } - - v8::Local in_call_key = utils::OneByteString(isolate, "in_call"); - bool in_call = config_object->Has(context, in_call_key).FromMaybe(false); - if (!in_call) - { - CHECK( - config_object->Set(context, in_call_key, v8::True(isolate)).FromJust()); - CHECK( - !inspector_method.As() - ->Call(context, info.Holder(), static_cast(call_args.size()), call_args.data()) - .IsEmpty()); - } - - v8::TryCatch try_catch(info.GetIsolate()); - static_cast(node_method.As()->Call( - context, info.Holder(), static_cast(call_args.size()), call_args.data())); - CHECK(config_object->Delete(context, in_call_key).FromJust()); - if (try_catch.HasCaught()) - try_catch.ReThrow(); - } - - void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) - { - - v8::Local array = - v8::Array::New(v8::Isolate::GetCurrent(), args.Length()); - CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 0, args[0]) - .FromJust()); - CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 1, args[1]) - .FromJust()); - CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 2, args[2]) - .FromJust()); - args.GetReturnValue().Set(v8::Function::New( - v8::Isolate::GetCurrent()->GetCurrentContext(), - InspectorConsoleCall, - array).ToLocalChecked()); - } - - void AgentImpl::Start(const unsigned short port, const std::string& appName) - { - port_ = port; - script_name_ = appName; - - // be sure server_ is not still in use here or its allocation will be replace in the thread func - // this can happen if reusing the same AgentImpl object, stopping and restarting before the InspectorSocketServer is properly pulled down - if (server_) { - throw std::runtime_error("can't start again the server as previous InspectorSocketServer is still active."); - } - - auto delegate = std::make_unique(*this, "", script_name_, false); - server_ = std::make_unique(std::move(delegate), port_); - server_->Start(); - } - - void AgentImpl::WaitForDebugger() - { - WaitForFrontendMessage(); - - while (waiting_for_frontend_) - DispatchMessages(); - - std::string reasonstr("Break on start"); - v8_inspector::StringView reason( - reinterpret_cast(reasonstr.c_str()), reasonstr.size()), - details( - reinterpret_cast(reasonstr.c_str()), - reasonstr.size()); - inspector_->SchedulePauseOnNextStatement(reason, details); - } - - void AgentImpl::Stop() - { - if (inspector_) - { - inspector_->DisconnectFrontend(); - inspector_.reset(); - } - - if (server_) - { - server_->Stop(); - } - } - - bool AgentImpl::IsConnected() - { - return server_ && server_->IsConnected(); - } - - bool AgentImpl::IsStarted() - { - return !!server_; - } - - std::unique_ptr ToProtocolString( - v8::Local value) - { - if (value.IsEmpty() || value->IsNull() || value->IsUndefined() || - !value->IsString()) - { - return v8_inspector::StringBuffer::create(v8_inspector::StringView()); - } - v8::Local string_value = v8::Local::Cast(value); - int len = string_value->Length(); - std::vector buffer(len, 0); - string_value->Write(v8::Isolate::GetCurrent(), buffer.data(), 0, len); - return v8_inspector::StringBuffer::create( - v8_inspector::StringView(buffer.data(), len)); - } - - bool AgentImpl::AppendMessage( - MessageQueue* queue, - int session_id, - std::unique_ptr buffer) - { - std::unique_lock lock(state_m); - bool trigger_pumping = queue->empty(); - queue->push_back(std::make_pair(session_id, std::move(buffer))); - return trigger_pumping; - } - - void AgentImpl::SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2) - { - std::unique_lock lock(state_m); - vector1->swap(*vector2); - } - - void AgentImpl::PostIncomingMessage( - int session_id, - const std::string& message) - { - if (AppendMessage(&incoming_message_queue_, session_id, utils::Utf8ToStringView(message))) - { - std::shared_ptr foregroundTaskRunner; - -#ifdef USE_DEFAULT_PLATFORM - // Need to get the foreground runner from the isolate data slot - v8runtime::IsolateData* isolate_data = reinterpret_cast(isolate_->GetData(v8runtime::ISOLATE_DATA_SLOT)); - foregroundTaskRunner = isolate_data->foreground_task_runner_; -#else - foregroundTaskRunner = platform_.GetForegroundTaskRunner(isolate_); -#endif - foregroundTaskRunner->PostTask(std::make_unique(*this)); - isolate_->RequestInterrupt(InterruptCallback, this); - } - NotifyMessageReceived(); - } - - void AgentImpl::WaitForFrontendMessage() - { - std::unique_lock lock(incoming_message_cond_m_); - if (incoming_message_queue_.empty()) - incoming_message_cond_.wait( - lock, [this] { return !incoming_message_queue_.empty(); }); - } - - void AgentImpl::NotifyMessageReceived() - { - incoming_message_cond_.notify_all(); - } - - void AgentImpl::DispatchMessages() - { - // This function can be reentered if there was an incoming message while - // V8 was processing another inspector request (e.g. if the user is - // evaluating a long-running JS code snippet). This can happen only at - // specific points (e.g. the lines that call inspector_ methods) - if (dispatching_messages_) - return; - dispatching_messages_ = true; - MessageQueue tasks; - do - { - tasks.clear(); - SwapBehindLock(&incoming_message_queue_, &tasks); - for (const MessageQueue::value_type& pair : tasks) - { - v8_inspector::StringView message = pair.second->string(); - std::string tag; - if (message.length() == sizeof(TAG_CONNECT) - 1 || - message.length() == sizeof(TAG_DISCONNECT) - 1) - { - tag = utils::StringViewToUtf8(message); - } - - if (tag == TAG_CONNECT) - { - session_id_ = pair.first; - inspector_->ConnectFrontend(); - } - else if (tag == TAG_DISCONNECT) - { - inspector_->DisconnectFrontend(); - } - else if (inspector_) - { - inspector_->DispatchMessageFromFrontend(message); - } - else - { -#ifdef WIN32 - OutputDebugStringW(L"Warning: V8 inspector message dropped - "); - if (message.is8Bit()) - { - OutputDebugStringA(reinterpret_cast(message.characters8())); - } - else - { - OutputDebugStringW(reinterpret_cast(message.characters16())); - } - OutputDebugStringW(L"\n"); -#endif - } - } - } while (!tasks.empty()); - dispatching_messages_ = false; - } - - void AgentImpl::Write( - int session_id, - std::unique_ptr inspector_message) - { - AppendMessage( - &outgoing_message_queue_, session_id, std::move(inspector_message)); - - MessageQueue outgoing_messages; - SwapBehindLock(&outgoing_message_queue_, &outgoing_messages); - for (const MessageQueue::value_type& outgoing : outgoing_messages) - { - v8_inspector::StringView view = outgoing.second->string(); - assert(server_); - if (server_) - { - if (view.length() == 0) - { - server_->Stop(); - } - else - { - server_->Send( - outgoing.first, utils::StringViewToUtf8(outgoing.second->string())); - } - } - } - } - - // Exported class Agent - V8InspectorAgent::V8InspectorAgent( - v8::Platform& platform, - v8::Isolate* isolate, - v8::Local context, - const char* context_name) - : impl(std::make_unique(platform, isolate, context, context_name)) - { - } - - V8InspectorAgent::~V8InspectorAgent() - { - } - - void V8InspectorAgent::WaitForDebugger() - { - impl->WaitForDebugger(); - } - - void V8InspectorAgent::Stop() - { - impl->Stop(); - } - - void V8InspectorAgent::Start(const unsigned short port, const std::string& appName) - { - impl->Start(port, appName); - } - - bool V8InspectorAgent::IsStarted() - { - return impl->IsStarted(); - } - - bool V8InspectorAgent::IsConnected() - { - return impl->IsConnected(); - } - - InspectorAgentDelegate::InspectorAgentDelegate( - AgentImpl& agent, - const std::string& script_path, - const std::string& script_name, - bool wait) - : agent_(agent) - , connected_(false) - , script_name_(script_name) - , script_path_(script_path) - , target_id_(utils::GenerateUniqueID()) - , waiting_(wait) - { - } - - void InspectorAgentDelegate::StartSession( - int session_id, - const std::string& /*target_id*/) - { - connected_ = true; - agent_.PostIncomingMessage(session_id, TAG_CONNECT); - } - - void InspectorAgentDelegate::MessageReceived( - int session_id, - const std::string& message) - { - // TODO(pfeldman): Instead of blocking execution while debugger - // engages, node should wait for the run callback from the remote client - // and initiate its startup. This is a change to node.cc that should be - // upstreamed separately. - if (waiting_) - { - if (message.find("\"Runtime.runIfWaitingForDebugger\"") != - std::string::npos) - { - waiting_ = false; - } - } - agent_.PostIncomingMessage(session_id, message); - } - - void InspectorAgentDelegate::EndSession(int session_id) - { - connected_ = false; - agent_.PostIncomingMessage(session_id, TAG_DISCONNECT); - } - - std::vector InspectorAgentDelegate::GetTargetIds() - { - return {target_id_}; - } - - std::string InspectorAgentDelegate::GetTargetTitle(const std::string& /*id*/) - { - return script_name_.empty() ? GetProcessTitle() : script_name_; - } - - std::string InspectorAgentDelegate::GetTargetUrl(const std::string& /*id*/) - { - return "file://" + script_path_; - } - -} // namespace inspector \ No newline at end of file diff --git a/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake b/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake new file mode 100644 index 000000000..84688ac91 --- /dev/null +++ b/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.21) + +if(NOT DEFINED source_dir) + message(FATAL_ERROR "source_dir not provided") +endif() + +if(NOT DEFINED patch_file) + message(FATAL_ERROR "patch_file not provided") +endif() + +execute_process( + COMMAND git -C "${source_dir}" apply --check "${patch_file}" + RESULT_VARIABLE check_result + OUTPUT_VARIABLE check_output + ERROR_VARIABLE check_error) +if(check_result EQUAL 0) + execute_process( + COMMAND git -C "${source_dir}" apply "${patch_file}" + RESULT_VARIABLE apply_result + OUTPUT_VARIABLE apply_output + ERROR_VARIABLE apply_error) + if(NOT apply_result EQUAL 0) + message(FATAL_ERROR "Failed to apply ${patch_file}: ${apply_error}") + endif() +else() + execute_process( + COMMAND git -C "${source_dir}" apply --reverse --check "${patch_file}" + RESULT_VARIABLE reverse_result + OUTPUT_VARIABLE reverse_output + ERROR_VARIABLE reverse_error) + if(reverse_result EQUAL 0) + message(STATUS "Patch ${patch_file} already applied") + else() + message(FATAL_ERROR "Failed to validate ${patch_file}: ${check_error}") + endif() +endif() diff --git a/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch b/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch new file mode 100644 index 000000000..da9687719 --- /dev/null +++ b/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch @@ -0,0 +1,13 @@ +--- a/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp ++++ b/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp +@@ -426,8 +426,8 @@ + } + v8::Local string_value = v8::Local::Cast(value); + int len = string_value->Length(); +- std::basic_string buffer(len, '\0'); +- string_value->Write(v8::Isolate::GetCurrent(), &buffer[0], 0, len); ++ std::vector buffer(len, 0); ++ string_value->Write(v8::Isolate::GetCurrent(), buffer.data(), 0, len); + return v8_inspector::StringBuffer::create( + v8_inspector::StringView(buffer.data(), len)); + } From 4bf9e4b3143a398fa0d361fb7cb2354029aef4fd Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 16:53:35 -0700 Subject: [PATCH 09/13] Declare Playground activity exported --- Apps/Playground/Android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps/Playground/Android/app/src/main/AndroidManifest.xml b/Apps/Playground/Android/app/src/main/AndroidManifest.xml index f33bace11..2959fb0a9 100644 --- a/Apps/Playground/Android/app/src/main/AndroidManifest.xml +++ b/Apps/Playground/Android/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/playground_activity"> From 3807487d28d02e2ea8287babc504bcc3aacb185c Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Sat, 27 Sep 2025 16:54:18 -0700 Subject: [PATCH 10/13] Remove libc++ char_traits flag workaround --- CMakeLists.txt | 8 -------- Install/Test/CMakeLists.txt | 5 ----- 2 files changed, 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67b1c40f1..94caf9565 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,14 +82,6 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") - # Restore the legacy std::char_traits specializations removed in newer libc++ versions - # so Chromium-derived dependencies that still rely on std::basic_string - # continue to build with the Android NDK r28 toolchain without requiring header overrides. - add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) - string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") -endif() - # -------------------------------------------------- # Options # -------------------------------------------------- diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index abdb5cf7b..a1bdae568 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -25,11 +25,6 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "Android") - add_compile_definitions(_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS) - string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_ENABLE_CXX20_REMOVED_CHAR_TRAITS_SPECIALIZATIONS") -endif() - if(WIN32 OR (APPLE AND NOT IOS) OR (UNIX AND NOT ANDROID AND NOT APPLE)) FetchContent_MakeAvailable_With_Message(googletest) endif() From fe1e86622bc18db30e9efe9c832149dc50e88c42 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:13:16 +0200 Subject: [PATCH 11/13] [CI maintenance] Fix test installation ios/macos + misc (#1550) - Add `dist/` TS build folder in gitIgnore - up to latest macOS for installation test iOS/macOS - up Babylon.js to 8.28.2 - Increased Timeout to 60min because D3D12 check is way slower that it was 2weeks ago - Disabled 2 Visual Tests with D3D12 - Moved screenshot capture method from Engine to TestUtils --- .github/jobs/win32.yml | 2 +- .gitignore | 2 +- Apps/Playground/Scripts/config.json | 8 ++-- Apps/Playground/Scripts/validation_native.js | 2 +- Apps/package-lock.json | 48 ++++++++++---------- Apps/package.json | 10 ++-- Plugins/NativeEngine/Source/NativeEngine.cpp | 16 ------- Plugins/NativeEngine/Source/NativeEngine.h | 1 - Plugins/TestUtils/Source/TestUtils.cpp | 14 ++++++ Plugins/TestUtils/Source/TestUtils.h | 16 ++++++- azure-pipelines.yml | 4 +- 11 files changed, 68 insertions(+), 55 deletions(-) diff --git a/.github/jobs/win32.yml b/.github/jobs/win32.yml index e73090f71..f716c63d4 100644 --- a/.github/jobs/win32.yml +++ b/.github/jobs/win32.yml @@ -18,7 +18,7 @@ parameters: jobs: - job: ${{ parameters.name }} - timeoutInMinutes: 20 + timeoutInMinutes: 60 pool: vmImage: ${{ parameters.vmImage }} variables: diff --git a/.gitignore b/.gitignore index 087c17bff..20514dfc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /build .DS_Store .vscode - +dist/ diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index c3d0df315..7f04d238d 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -115,9 +115,9 @@ "title": "Glow layer and LODs", "playgroundId": "#UNS6ZV#2", "renderCount": 50, - "excludedGraphicsApis": [ "OpenGL" ], + "excludedGraphicsApis": [ "OpenGL", "D3D12" ], "referenceImage": "glowlayerandlods.png", - "comment": "xvfb : invalid enum with glRenderbufferStorage" + "comment": "xvfb : invalid enum with glRenderbufferStorage. D3D12/CI: mapping a texture fails with error HRESULT 0x887A0005" }, { "title": "Nested BBG", @@ -128,7 +128,9 @@ { "title": "Dynamic Texture context clip", "playgroundId": "#FU0ES5#47", - "referenceImage": "dynamicTextureClip.png" + "referenceImage": "dynamicTextureClip.png", + "excludedGraphicsApis": ["D3D12" ], + "comment": "D3D12/CI: Incorrect rendering of clipped texture" }, { "title": "GUI3D SpherePanel", diff --git a/Apps/Playground/Scripts/validation_native.js b/Apps/Playground/Scripts/validation_native.js index be67a4f1c..7445c3c7d 100644 --- a/Apps/Playground/Scripts/validation_native.js +++ b/Apps/Playground/Scripts/validation_native.js @@ -81,7 +81,7 @@ } function evaluate(test, referenceImage, done, compareFunction) { - engine._engine.getFrameBufferData(function (screenshot) { + TestUtils.getFrameBufferData(function (screenshot) { let testRes = true; if (!test.onlyVisual) { diff --git a/Apps/package-lock.json b/Apps/package-lock.json index 52a47cb42..aca8e20fd 100644 --- a/Apps/package-lock.json +++ b/Apps/package-lock.json @@ -11,11 +11,11 @@ "UnitTests" ], "dependencies": { - "babylonjs": "^8.17.0", - "babylonjs-gltf2interface": "^8.17.0", - "babylonjs-gui": "^8.17.0", - "babylonjs-loaders": "^8.17.0", - "babylonjs-materials": "^8.17.0", + "babylonjs": "^8.28.2", + "babylonjs-gltf2interface": "^8.28.2", + "babylonjs-gui": "^8.28.2", + "babylonjs-loaders": "^8.28.2", + "babylonjs-materials": "^8.28.2", "jsc-android": "^241213.1.0", "v8-android": "^7.8.2" } @@ -2600,44 +2600,44 @@ } }, "node_modules/babylonjs": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-8.17.0.tgz", - "integrity": "sha512-XLMAk+R/9wZMhwdRZFGO6z8rrJvvvhsiztHA0NLvi9hcdxbsdTwA4Ic5Rscqy+9O11hWSBjjuEuGsSrVXdJZDg==", + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-8.28.2.tgz", + "integrity": "sha512-mid1cYg2VGXKj4neNy+78MHwSV8tMCHbvSEaIXQuEj1JxKmEb+UQ6/XwqiSGMrw1IoFxRZ693kelfe1tuSdKJg==", "hasInstallScript": true, "license": "Apache-2.0" }, "node_modules/babylonjs-gltf2interface": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-8.17.0.tgz", - "integrity": "sha512-EdfH3PTIY5U1Y+gUj/mSw/fW+fUvTEZWryOBaXF4nwZzaFOfhnG/+kJ1gNEE25lNbOHOYlYw0k0S6eMaw1UJBA==", + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-8.28.2.tgz", + "integrity": "sha512-TGusApjAPNkPhyyU/05FNoiEc9l85Kd4ZGHO/5hsRkLLYVXvmsqAEa9yGIVLhYM3Yezf/b1DFwMPvY1D/NEiVA==", "license": "Apache-2.0" }, "node_modules/babylonjs-gui": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-8.17.0.tgz", - "integrity": "sha512-8pYk22jPkeOJGKZpgdox9CK+FmdZO5w9MbVMaCCYeodtAS/9QhETYsdd4LuWG7hHhFITaiIDn9FnlyaclbMTrg==", + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-8.28.2.tgz", + "integrity": "sha512-sFURHbGEfPiRPGQM4jKsPbG4ZxWj9cejNbaxbjNMCB8izc33rzXB67+yHxDdSfjkiEeB1VM30OdhdEW6/JAq+g==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "^8.17.0" + "babylonjs": "^8.28.2" } }, "node_modules/babylonjs-loaders": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-8.17.0.tgz", - "integrity": "sha512-mkEw4aXIkPI4Ef/Wjvdv6L2UVvQ2bINYpdflYqzo77biWCm0Oq5H/QODYgnHv/EUe0RGPgGbO+T8HyLtUsb8lg==", + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-8.28.2.tgz", + "integrity": "sha512-WIbR3gaHhxSvRzs7Yuz578YlhPXqlB36tEEqIo0E+s5FqgzFy+o2TBN0O8nWnGuyk7s4wAZpA8CBQ58KsGfw5Q==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "^8.17.0", - "babylonjs-gltf2interface": "^8.17.0" + "babylonjs": "^8.28.2", + "babylonjs-gltf2interface": "^8.28.2" } }, "node_modules/babylonjs-materials": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-8.17.0.tgz", - "integrity": "sha512-iS6gUSUlhNUcKlmaVvXq0PyrGmYR+LyVr3uSpIVUKm9svZ/fAsZEEQ+riRRKFYGdsUnhfqDVPsHv2YSfoLNQgA==", + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-8.28.2.tgz", + "integrity": "sha512-5/pURv40ugE7tIeiuW/6C4Eo6VXOcHZJhtjrwdLDlpfMFpAU7d2JTNyaa9NqnHkRVRqXtad3/cu6FoVw6+AsZw==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "^8.17.0" + "babylonjs": "^8.28.2" } }, "node_modules/balanced-match": { diff --git a/Apps/package.json b/Apps/package.json index 3eb661afa..10eb897c7 100644 --- a/Apps/package.json +++ b/Apps/package.json @@ -9,11 +9,11 @@ "getNightly": "node scripts/getNightly.js" }, "dependencies": { - "babylonjs": "^8.17.0", - "babylonjs-gltf2interface": "^8.17.0", - "babylonjs-gui": "^8.17.0", - "babylonjs-loaders": "^8.17.0", - "babylonjs-materials": "^8.17.0", + "babylonjs": "^8.28.2", + "babylonjs-gltf2interface": "^8.28.2", + "babylonjs-gui": "^8.28.2", + "babylonjs-loaders": "^8.28.2", + "babylonjs-materials": "^8.28.2", "jsc-android": "^241213.1.0", "v8-android": "^7.8.2" } diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index f933dc91c..b443db117 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -728,8 +728,6 @@ namespace Babylon InstanceMethod("populateFrameStats", &NativeEngine::PopulateFrameStats), - // REVIEW: Should this be here if only used by ValidationTest? - InstanceMethod("getFrameBufferData", &NativeEngine::GetFrameBufferData), InstanceMethod("setDeviceLostCallback", &NativeEngine::SetRenderResetCallback), }); @@ -2188,20 +2186,6 @@ namespace Babylon }); } - void NativeEngine::GetFrameBufferData(const Napi::CallbackInfo& info) - { - const auto callback{info[0].As()}; - - auto callbackPtr{std::make_shared(Napi::Persistent(callback))}; - m_deviceContext.RequestScreenShot([this, callbackPtr{std::move(callbackPtr)}](std::vector array) { - m_runtime.Dispatch([callbackPtr{std::move(callbackPtr)}, array{std::move(array)}](Napi::Env env) { - auto arrayBuffer{Napi::ArrayBuffer::New(env, const_cast(array.data()), array.size())}; - auto typedArray{Napi::Uint8Array::New(env, array.size(), arrayBuffer, 0)}; - callbackPtr->Value().Call({typedArray}); - }); - }); - } - void NativeEngine::SetStencil(NativeDataStream::Reader& data) { const uint32_t writeMask{data.ReadUint32()}; diff --git a/Plugins/NativeEngine/Source/NativeEngine.h b/Plugins/NativeEngine/Source/NativeEngine.h index 6b7690149..2eeecf5df 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.h +++ b/Plugins/NativeEngine/Source/NativeEngine.h @@ -11,7 +11,6 @@ #include #include #include -#include #include diff --git a/Plugins/TestUtils/Source/TestUtils.cpp b/Plugins/TestUtils/Source/TestUtils.cpp index 7ec3ad500..2ef382ccd 100644 --- a/Plugins/TestUtils/Source/TestUtils.cpp +++ b/Plugins/TestUtils/Source/TestUtils.cpp @@ -71,4 +71,18 @@ namespace Babylon::Plugins::Internal return Napi::Value::From(info.Env(), data); } + + void TestUtils::GetFrameBufferData(const Napi::CallbackInfo& info) + { + const auto callback{ info[0].As() }; + + auto callbackPtr{ std::make_shared(Napi::Persistent(callback)) }; + m_deviceContext.RequestScreenShot([this, callbackPtr{ std::move(callbackPtr) }](std::vector array) { + m_runtime.Dispatch([callbackPtr{ std::move(callbackPtr) }, array{ std::move(array) }](Napi::Env env) { + auto arrayBuffer{ Napi::ArrayBuffer::New(env, const_cast(array.data()), array.size()) }; + auto typedArray{ Napi::Uint8Array::New(env, array.size(), arrayBuffer, 0) }; + callbackPtr->Value().Call({ typedArray }); + }); + }); + } } diff --git a/Plugins/TestUtils/Source/TestUtils.h b/Plugins/TestUtils/Source/TestUtils.h index 8b0eeb49e..d3e625e16 100644 --- a/Plugins/TestUtils/Source/TestUtils.h +++ b/Plugins/TestUtils/Source/TestUtils.h @@ -7,6 +7,8 @@ #include #include +#include +#include namespace Babylon::Plugins::Internal { @@ -36,12 +38,20 @@ namespace Babylon::Plugins::Internal ParentT::InstanceMethod("decodeImage", &TestUtils::DecodeImage), ParentT::InstanceMethod("getImageData", &TestUtils::GetImageData), ParentT::InstanceMethod("getOutputDirectory", &TestUtils::GetOutputDirectory), + ParentT::InstanceMethod("getFrameBufferData", &TestUtils::GetFrameBufferData), }); env.Global().Set(JS_INSTANCE_NAME, func.New({})); } - explicit TestUtils(const Napi::CallbackInfo& info) + TestUtils(const Napi::CallbackInfo& info) + : TestUtils(info, JsRuntime::GetFromJavaScript(info.Env())) + { + } + + explicit TestUtils(const Napi::CallbackInfo& info, JsRuntime& runtime) : ParentT{info} + , m_runtime{runtime} + , m_deviceContext{ Graphics::DeviceContext::GetFromJavaScript(info.Env()) } { } @@ -59,6 +69,10 @@ namespace Babylon::Plugins::Internal void WritePNG(const Napi::CallbackInfo& info); Napi::Value DecodeImage(const Napi::CallbackInfo& info); Napi::Value GetImageData(const Napi::CallbackInfo& info); + void GetFrameBufferData(const Napi::CallbackInfo& info); + + JsRuntime& m_runtime; + Graphics::DeviceContext& m_deviceContext; struct Image { diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 526d385a2..2e8971a21 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -141,11 +141,11 @@ jobs: - template: .github/jobs/test_install_macos.yml parameters: name: MacOS_Installation - vmImage: 'macOS-14' + vmImage: 'macOS-latest' - template: .github/jobs/test_install_ios.yml parameters: name: iOS_Installation - vmImage: 'macOS-14' + vmImage: 'macOS-latest' deploymentTarget: 17.2 # These Android tests are unstable on the CI emulator. Disabled until a more reliable solution is found. From cd9de0e7155634f2b9c3fb05bcaf67f538713294 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:56:27 +0200 Subject: [PATCH 12/13] win32 installation (#1553) --- Install/Test/CMakeLists.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Install/Test/CMakeLists.txt b/Install/Test/CMakeLists.txt index a1bdae568..299093841 100644 --- a/Install/Test/CMakeLists.txt +++ b/Install/Test/CMakeLists.txt @@ -88,13 +88,7 @@ endif() set(APPS_DIR "../../Apps") set(UNITTESTS_DIR "${APPS_DIR}/UnitTests") -set(LOCAL_SCRIPTS "${UNITTESTS_DIR}/Scripts/tests.js") - -set(NPM_SCRIPTS - "${APPS_DIR}/node_modules/babylonjs/babylon.max.js" - "${APPS_DIR}/node_modules/babylonjs-materials/babylonjs.materials.js" - "${APPS_DIR}/node_modules/chai/chai.js" - "${APPS_DIR}/node_modules/mocha/mocha.js") +set(LOCAL_SCRIPTS "${UNITTESTS_DIR}/dist/tests.js") set(SOURCES "${UNITTESTS_DIR}/Shared/Shared.h" @@ -203,7 +197,7 @@ elseif(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8") endif() endif() -add_executable(TestInstall ${LOCAL_SCRIPTS} ${NPM_SCRIPTS} ${SOURCES}) +add_executable(TestInstall ${LOCAL_SCRIPTS} ${SOURCES}) set(INSTALL_LIBS_DIR "${BINARY_DIR}/install/lib") target_include_directories(TestInstall PRIVATE "${BINARY_DIR}/install/include") From ef619c2f7b4b398c560ebaf684f1330e1a86065d Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Fri, 10 Oct 2025 17:17:43 -0700 Subject: [PATCH 13/13] Remove JsRuntimeHost patch for NDK 28 libc++ compatibility. Point at JsRuntimeHost PR branch to test integration. macOS tests build and pass. --- Apps/Playground/Android/gradlew | 0 Apps/package-lock.json | 12 +++++-- CMakeLists.txt | 8 ++--- .../Patches/JsRuntimeHost/apply_patch.cmake | 36 ------------------- .../v8inspector-utf16-vector.patch | 13 ------- 5 files changed, 12 insertions(+), 57 deletions(-) mode change 100644 => 100755 Apps/Playground/Android/gradlew delete mode 100644 Dependencies/Patches/JsRuntimeHost/apply_patch.cmake delete mode 100644 Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch diff --git a/Apps/Playground/Android/gradlew b/Apps/Playground/Android/gradlew old mode 100644 new mode 100755 diff --git a/Apps/package-lock.json b/Apps/package-lock.json index aca8e20fd..1b632a635 100644 --- a/Apps/package-lock.json +++ b/Apps/package-lock.json @@ -95,6 +95,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1924,7 +1925,8 @@ "version": "8.25.1", "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-8.25.1.tgz", "integrity": "sha512-MxnXfRzHe/JzkGz1w/rtmLaU/m93aatqgOnMiXP6lP7a81LodA5nxL48KTHcIgz3cf5ChFr8n2qNkCjc049NIA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@babylonjs/gui": { "version": "8.25.1", @@ -2402,6 +2404,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2428,6 +2431,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2610,7 +2614,8 @@ "version": "8.28.2", "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-8.28.2.tgz", "integrity": "sha512-TGusApjAPNkPhyyU/05FNoiEc9l85Kd4ZGHO/5hsRkLLYVXvmsqAEa9yGIVLhYM3Yezf/b1DFwMPvY1D/NEiVA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/babylonjs-gui": { "version": "8.28.2", @@ -2732,6 +2737,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -5280,6 +5286,7 @@ "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -5329,6 +5336,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/CMakeLists.txt b/CMakeLists.txt index 94caf9565..983b584dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,8 @@ FetchContent_Declare(ios-cmake GIT_REPOSITORY https://github.com/leetal/ios-cmake.git GIT_TAG 4.5.0) FetchContent_Declare(JsRuntimeHost - GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git - GIT_TAG c726f284ab906f2f535ac17c3c49cc18c0ea5267 - PATCH_COMMAND ${CMAKE_COMMAND} - -Dpatch_file=${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch - -Dsource_dir= - -P ${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake) + GIT_REPOSITORY https://github.com/rebeckerspecialties/JsRuntimeHost.git + GIT_TAG update-jsruntimehost-for-android-xr-compatibility) FetchContent_Declare(SPIRV-Cross GIT_REPOSITORY https://github.com/BabylonJS/SPIRV-Cross.git GIT_TAG 6abfcf066d171e9ade7604d91381ebebe4209edc) diff --git a/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake b/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake deleted file mode 100644 index 84688ac91..000000000 --- a/Dependencies/Patches/JsRuntimeHost/apply_patch.cmake +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.21) - -if(NOT DEFINED source_dir) - message(FATAL_ERROR "source_dir not provided") -endif() - -if(NOT DEFINED patch_file) - message(FATAL_ERROR "patch_file not provided") -endif() - -execute_process( - COMMAND git -C "${source_dir}" apply --check "${patch_file}" - RESULT_VARIABLE check_result - OUTPUT_VARIABLE check_output - ERROR_VARIABLE check_error) -if(check_result EQUAL 0) - execute_process( - COMMAND git -C "${source_dir}" apply "${patch_file}" - RESULT_VARIABLE apply_result - OUTPUT_VARIABLE apply_output - ERROR_VARIABLE apply_error) - if(NOT apply_result EQUAL 0) - message(FATAL_ERROR "Failed to apply ${patch_file}: ${apply_error}") - endif() -else() - execute_process( - COMMAND git -C "${source_dir}" apply --reverse --check "${patch_file}" - RESULT_VARIABLE reverse_result - OUTPUT_VARIABLE reverse_output - ERROR_VARIABLE reverse_error) - if(reverse_result EQUAL 0) - message(STATUS "Patch ${patch_file} already applied") - else() - message(FATAL_ERROR "Failed to validate ${patch_file}: ${check_error}") - endif() -endif() diff --git a/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch b/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch deleted file mode 100644 index da9687719..000000000 --- a/Dependencies/Patches/JsRuntimeHost/v8inspector-utf16-vector.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp -+++ b/Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp -@@ -426,8 +426,8 @@ - } - v8::Local string_value = v8::Local::Cast(value); - int len = string_value->Length(); -- std::basic_string buffer(len, '\0'); -- string_value->Write(v8::Isolate::GetCurrent(), &buffer[0], 0, len); -+ std::vector buffer(len, 0); -+ string_value->Write(v8::Isolate::GetCurrent(), buffer.data(), 0, len); - return v8_inspector::StringBuffer::create( - v8_inspector::StringView(buffer.data(), len)); - }