From 8bac865b5a5b8d76e9203587359569a67c2b98cd Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 16 Jul 2025 06:29:10 -0700 Subject: [PATCH] Use native helpers to accelerate transform processing (#52603) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52603 Processing transforms is expensive in Java, as it requires bridging the entire ReadableNativeArray/Map. Instead, we can use the existing parser logic `resolveTransform` logic to perform this operation in C++. Ideally, we actually re-use the existing parsed transform from Props, that could be something we revisit after Props 2.0. As a follow-up, we should consider also moving the matrix decomposition logic from MatrixMathHelper here, and make that the only information we send back to Java. Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D78298588 fbshipit-source-id: a698ac8587ccfb2be04665747082398ccdde9294 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 ++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++- ...agsOverrides_RNOSS_Experimental_Android.kt | 4 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../react/uimanager/TransformHelper.kt | 19 ++++++ .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../src/main/jni/react/jni/NativeArray.h | 4 ++ .../src/main/jni/react/jni/OnLoad-common.cpp | 2 + .../main/jni/react/jni/TransformHelper.cpp | 60 +++++++++++++++++++ .../src/main/jni/react/jni/TransformHelper.h | 22 +++++++ .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 ++- .../ReactNativeFeatureFlagsAccessor.cpp | 34 ++++++++--- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +++- ...tiveFeatureFlagsOverridesOSSExperimental.h | 6 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 ++- .../NativeReactNativeFeatureFlags.h | 4 +- .../components/view/BaseViewProps.cpp | 10 +++- .../renderer/components/view/BaseViewProps.h | 5 ++ .../ReactNativeFeatureFlags.config.js | 10 ++++ .../featureflags/ReactNativeFeatureFlags.js | 7 ++- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 29 files changed, 271 insertions(+), 31 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index e7a65af6bd8..8a10052cd03 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<57a128d47d69507e15f768970753c89e>> + * @generated SignedSource<> */ /** @@ -348,6 +348,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = accessor.useNativeEqualsInNativeReadableArrayAndroid() + /** + * Use a native implementation of TransformHelper + */ + @JvmStatic + public fun useNativeTransformHelperAndroid(): Boolean = accessor.useNativeTransformHelperAndroid() + /** * When enabled, the native view configs are used in bridgeless mode. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 3656100da63..4c31c559aa4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<8e0125e82b359e6a175ffc49a4df5537>> */ /** @@ -73,6 +73,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null + private var useNativeTransformHelperAndroidCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null private var useRawPropsJsiValueCache: Boolean? = null @@ -558,6 +559,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun useNativeTransformHelperAndroid(): Boolean { + var cached = useNativeTransformHelperAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useNativeTransformHelperAndroid() + useNativeTransformHelperAndroidCache = cached + } + return cached + } + override fun useNativeViewConfigsInBridgelessMode(): Boolean { var cached = useNativeViewConfigsInBridgelessModeCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 4d22fc6b607..915e42883fc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1729eb15adfaaf4daec98240ddc78ca0>> + * @generated SignedSource<> */ /** @@ -134,6 +134,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useNativeTransformHelperAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean @DoNotStrip @JvmStatic public external fun useOptimizedEventBatchingOnAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 63e89305786..70dc144b28f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<10d708ce4449eede46d750a1ed48d02e>> */ /** @@ -129,6 +129,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = false + override fun useNativeTransformHelperAndroid(): Boolean = false + override fun useNativeViewConfigsInBridgelessMode(): Boolean = false override fun useOptimizedEventBatchingOnAndroid(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index b5527bbbd4e..b5aa6b46dd7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5f923f20a4d5da0935058ab99fd264b3>> + * @generated SignedSource<> */ /** @@ -77,6 +77,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null + private var useNativeTransformHelperAndroidCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null private var useRawPropsJsiValueCache: Boolean? = null @@ -615,6 +616,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun useNativeTransformHelperAndroid(): Boolean { + var cached = useNativeTransformHelperAndroidCache + if (cached == null) { + cached = currentProvider.useNativeTransformHelperAndroid() + accessedFeatureFlags.add("useNativeTransformHelperAndroid") + useNativeTransformHelperAndroidCache = cached + } + return cached + } + override fun useNativeViewConfigsInBridgelessMode(): Boolean { var cached = useNativeViewConfigsInBridgelessModeCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt index fcb8270dd4f..1b8ad3bdc5c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0bafb0a2fb79c4220d21f1736894af14>> */ /** @@ -26,4 +26,6 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android : override fun preventShadowTreeCommitExhaustion(): Boolean = true override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = true + + override fun useNativeTransformHelperAndroid(): Boolean = true } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 9199d3901e5..de14fb57768 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<95a5bbbe7e05ff51434ff060ef94b74f>> + * @generated SignedSource<<21704207ce520def05b05f89dfba1048>> */ /** @@ -129,6 +129,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean + @DoNotStrip public fun useNativeTransformHelperAndroid(): Boolean + @DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean @DoNotStrip public fun useOptimizedEventBatchingOnAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt index 88313b35fea..f72ab667427 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt @@ -8,10 +8,12 @@ package com.facebook.react.uimanager import com.facebook.common.logging.FLog +import com.facebook.react.bridge.NativeArray import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.facebook.react.common.ReactConstants +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags public object TransformHelper { @@ -69,6 +71,14 @@ public object TransformHelper { transformOrigin: ReadableArray?, allowPercentageResolution: Boolean ) { + if (allowPercentageResolution && + ReactNativeFeatureFlags.useNativeTransformHelperAndroid() && + transforms is NativeArray && + transformOrigin is NativeArray?) { + nativeProcessTransform(transforms, result, viewWidth, viewHeight, transformOrigin) + return + } + val helperMatrix = helperMatrix.get()!! MatrixMathHelper.resetIdentityMatrix(result) val offsets = @@ -220,4 +230,13 @@ public object TransformHelper { return doubleArrayOf(newTranslateX, newTranslateY, newTranslateZ) } + + @JvmStatic + private external fun nativeProcessTransform( + transforms: NativeArray, + result: DoubleArray, + viewWidth: Float, + viewHeight: Float, + transformOrigin: NativeArray? + ) } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 93b7579647f..5ae6ba010df 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0c0ccd76192aba5c7bf22b1655f53b62>> + * @generated SignedSource<> */ /** @@ -357,6 +357,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool useNativeTransformHelperAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useNativeTransformHelperAndroid"); + return method(javaProvider_); + } + bool useNativeViewConfigsInBridgelessMode() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useNativeViewConfigsInBridgelessMode"); @@ -668,6 +674,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAnd return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useNativeTransformHelperAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(); @@ -893,6 +904,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useNativeEqualsInNativeReadableArrayAndroid", JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid), + makeNativeMethod( + "useNativeTransformHelperAndroid", + JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid), makeNativeMethod( "useNativeViewConfigsInBridgelessMode", JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 67edbf456ec..a04ebe7fc0e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2330cbab72534dd16218a09c5d5c8bf5>> + * @generated SignedSource<<57f2dcf4b71512c6b15e8021258d6036>> */ /** @@ -189,6 +189,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useNativeEqualsInNativeReadableArrayAndroid( facebook::jni::alias_ref); + static bool useNativeTransformHelperAndroid( + facebook::jni::alias_ref); + static bool useNativeViewConfigsInBridgelessMode( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h index c7087485521..738738437b0 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h @@ -21,6 +21,10 @@ class NativeArray : public jni::HybridClass { jni::local_ref toString(); + const folly::dynamic& getArray() const { + return array_; + } + RN_EXPORT folly::dynamic consume(); // Whether this array has been added to another array or map and no longer diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp index 293d2ffc8ab..ef069ed7e41 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp @@ -11,6 +11,7 @@ #include "JReactMarker.h" #include "NativeArray.h" #include "NativeMap.h" +#include "TransformHelper.h" #include "WritableNativeArray.h" #include "WritableNativeMap.h" @@ -27,6 +28,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { ReadableNativeMap::registerNatives(); WritableNativeArray::registerNatives(); WritableNativeMap::registerNatives(); + TransformHelper::registerNatives(); }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp new file mode 100644 index 00000000000..b33d269ac29 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TransformHelper.h" + +#include +#include + +#include "NativeArray.h" + +using namespace facebook::jni; + +namespace facebook::react { + +namespace { +void processTransform( + jni::alias_ref /*unused*/, + NativeArray* jTransforms, + jni::alias_ref jResult, + float viewWidth, + float viewHeight, + NativeArray* jTransformOrigin) { + // Assuming parsing transforms doesn't require a real PropsParserContext + static ContextContainer contextContainer; + static PropsParserContext context(0, contextContainer); + + RawValue transformValue(jTransforms->getArray()); + Transform transform; + fromRawValue(context, transformValue, transform); + + TransformOrigin transformOrigin; + if (jTransformOrigin != nullptr) { + RawValue transformOriginValue(jTransformOrigin->getArray()); + fromRawValue(context, transformOriginValue, transformOrigin); + } + + auto result = BaseViewProps::resolveTransform( + Size(viewWidth, viewHeight), transform, transformOrigin); + + // Convert from matrix of floats to double matrix + constexpr size_t MatrixSize = std::tuple_size_v; + std::array doubleTransform{}; + std::copy( + result.matrix.begin(), result.matrix.end(), doubleTransform.begin()); + jResult->setRegion(0, MatrixSize, doubleTransform.data()); +} + +} // namespace + +void TransformHelper::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod("nativeProcessTransform", processTransform), + }); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h new file mode 100644 index 00000000000..342a8ee1100 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::react { + +class TransformHelper : public jni::JavaClass { + public: + static auto constexpr* kJavaDescriptor = + "Lcom/facebook/react/uimanager/TransformHelper;"; + + static void registerNatives(); +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index daf2677e133..667aec1cb3f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<2cabd888b74b84201ff027457efc6007>> */ /** @@ -238,6 +238,10 @@ bool ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid() { return getAccessor().useNativeEqualsInNativeReadableArrayAndroid(); } +bool ReactNativeFeatureFlags::useNativeTransformHelperAndroid() { + return getAccessor().useNativeTransformHelperAndroid(); +} + bool ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode() { return getAccessor().useNativeViewConfigsInBridgelessMode(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index e0014d78a5c..564d82bba30 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0eff4b5f07581cdf3f200924a9a3bb42>> + * @generated SignedSource<> */ /** @@ -304,6 +304,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useNativeEqualsInNativeReadableArrayAndroid(); + /** + * Use a native implementation of TransformHelper + */ + RN_EXPORT static bool useNativeTransformHelperAndroid(); + /** * When enabled, the native view configs are used in bridgeless mode. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 740afd9875e..d3b0351a093 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2240305552c24ef91dcd4b2c87267e28>> + * @generated SignedSource<<7ae9a203a94e3a22197bc9eda69b741c>> */ /** @@ -983,6 +983,24 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { + auto flagValue = useNativeTransformHelperAndroid_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(53, "useNativeTransformHelperAndroid"); + + flagValue = currentProvider_->useNativeTransformHelperAndroid(); + useNativeTransformHelperAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { auto flagValue = useNativeViewConfigsInBridgelessMode_.load(); @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(54, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(55, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "useRawPropsJsiValue"); + markFlagAsAccessed(56, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "useShadowNodeStateOnClone"); + markFlagAsAccessed(57, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useTurboModuleInterop"); + markFlagAsAccessed(58, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useTurboModules"); + markFlagAsAccessed(59, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1100,7 +1118,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "virtualViewPrerenderRatio"); + markFlagAsAccessed(60, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 613efec36c7..8e2acc5b241 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<257e707937b438617004798572dfac2a>> + * @generated SignedSource<<74560113d0b23c05d7822eeba1c0dee4>> */ /** @@ -85,6 +85,7 @@ class ReactNativeFeatureFlagsAccessor { bool useAlwaysAvailableJSErrorHandling(); bool useFabricInterop(); bool useNativeEqualsInNativeReadableArrayAndroid(); + bool useNativeTransformHelperAndroid(); bool useNativeViewConfigsInBridgelessMode(); bool useOptimizedEventBatchingOnAndroid(); bool useRawPropsJsiValue(); @@ -103,7 +104,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 60> accessedFeatureFlags_; + std::array, 61> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> animatedShouldSignalBatch_; @@ -158,6 +159,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useAlwaysAvailableJSErrorHandling_; std::atomic> useFabricInterop_; std::atomic> useNativeEqualsInNativeReadableArrayAndroid_; + std::atomic> useNativeTransformHelperAndroid_; std::atomic> useNativeViewConfigsInBridgelessMode_; std::atomic> useOptimizedEventBatchingOnAndroid_; std::atomic> useRawPropsJsiValue_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index c761b9dcc02..e0742c9c5c9 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0fb260ec04c9d43bebfac5a5d7726bd0>> + * @generated SignedSource<<1a54000b8eb51cb91304902c7f722d45>> */ /** @@ -239,6 +239,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool useNativeTransformHelperAndroid() override { + return false; + } + bool useNativeViewConfigsInBridgelessMode() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index e1c6654719d..f5c85f530a4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<244db790cc754f31402981967cd902b5>> */ /** @@ -522,6 +522,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useNativeEqualsInNativeReadableArrayAndroid(); } + bool useNativeTransformHelperAndroid() override { + auto value = values_["useNativeTransformHelperAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useNativeTransformHelperAndroid(); + } + bool useNativeViewConfigsInBridgelessMode() override { auto value = values_["useNativeViewConfigsInBridgelessMode"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index deefb4238e4..230dc476cef 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -34,6 +34,10 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur bool useNativeEqualsInNativeReadableArrayAndroid() override { return true; } + + bool useNativeTransformHelperAndroid() override { + return true; + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 5300e3a1e32..e9dd92d9494 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4b2d5467c78636794139aa5b67fa5c0c>> + * @generated SignedSource<<2387ed12fe46fb5b606ad13a17511f03>> */ /** @@ -78,6 +78,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useAlwaysAvailableJSErrorHandling() = 0; virtual bool useFabricInterop() = 0; virtual bool useNativeEqualsInNativeReadableArrayAndroid() = 0; + virtual bool useNativeTransformHelperAndroid() = 0; virtual bool useNativeViewConfigsInBridgelessMode() = 0; virtual bool useOptimizedEventBatchingOnAndroid() = 0; virtual bool useRawPropsJsiValue() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index a899f9d1dc4..1e9e0ffc9b2 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<45e1b08fd2438b27af82591b5cfa5744>> */ /** @@ -309,6 +309,11 @@ bool NativeReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid( return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(); } +bool NativeReactNativeFeatureFlags::useNativeTransformHelperAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useNativeTransformHelperAndroid(); +} + bool NativeReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 09c28dc1605..bc8014a5d80 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<439c0e9cf31e443b5678665a9cc8e789>> + * @generated SignedSource<> */ /** @@ -142,6 +142,8 @@ class NativeReactNativeFeatureFlags bool useNativeEqualsInNativeReadableArrayAndroid(jsi::Runtime& runtime); + bool useNativeTransformHelperAndroid(jsi::Runtime& runtime); + bool useNativeViewConfigsInBridgelessMode(jsi::Runtime& runtime); bool useOptimizedEventBatchingOnAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 690aedc3501..5bf3c6e4179 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -550,6 +550,13 @@ BorderMetrics BaseViewProps::resolveBorderMetrics( Transform BaseViewProps::resolveTransform( const LayoutMetrics& layoutMetrics) const { const auto& frameSize = layoutMetrics.frame.size; + return resolveTransform(frameSize, transform, transformOrigin); +} + +Transform BaseViewProps::resolveTransform( + const Size& frameSize, + const Transform& transform, + const TransformOrigin& transformOrigin) { auto transformMatrix = Transform{}; if (frameSize.width == 0 && frameSize.height == 0) { return transformMatrix; @@ -562,8 +569,7 @@ Transform BaseViewProps::resolveTransform( } else { for (const auto& operation : transform.operations) { transformMatrix = transformMatrix * - Transform::FromTransformOperation( - operation, layoutMetrics.frame.size, transform); + Transform::FromTransformOperation(operation, frameSize, transform); } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index f7e56418ef0..6258d1375da 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -115,6 +115,11 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { Transform resolveTransform(const LayoutMetrics& layoutMetrics) const; bool getClipsContentToBounds() const; + static Transform resolveTransform( + const Size& frameSize, + const Transform& transform, + const TransformOrigin& transformOrigin); + #if RN_DEBUG_STRING_CONVERTIBLE SharedDebugStringConvertibleList getDebugProps() const override; #endif diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 0894ee33bde..feb33a2d55d 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -606,6 +606,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'experimental', }, + useNativeTransformHelperAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-15', + description: 'Use a native implementation of TransformHelper', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, useNativeViewConfigsInBridgelessMode: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 5a143a6339c..108ed513497 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -104,6 +104,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useAlwaysAvailableJSErrorHandling: Getter, useFabricInterop: Getter, useNativeEqualsInNativeReadableArrayAndroid: Getter, + useNativeTransformHelperAndroid: Getter, useNativeViewConfigsInBridgelessMode: Getter, useOptimizedEventBatchingOnAndroid: Getter, useRawPropsJsiValue: Getter, @@ -409,6 +410,10 @@ export const useFabricInterop: Getter = createNativeFlagGetter('useFabr * Use a native implementation of equals in NativeReadableArray. */ export const useNativeEqualsInNativeReadableArrayAndroid: Getter = createNativeFlagGetter('useNativeEqualsInNativeReadableArrayAndroid', false); +/** + * Use a native implementation of TransformHelper + */ +export const useNativeTransformHelperAndroid: Getter = createNativeFlagGetter('useNativeTransformHelperAndroid', false); /** * When enabled, the native view configs are used in bridgeless mode. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 78a582cd61e..4558b60203c 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1c47c6a2547badec31526142e4fc1798>> + * @generated SignedSource<<8b4f1275a16d5b83f5594da1eb89c6c1>> * @flow strict * @noformat */ @@ -78,6 +78,7 @@ export interface Spec extends TurboModule { +useAlwaysAvailableJSErrorHandling?: () => boolean; +useFabricInterop?: () => boolean; +useNativeEqualsInNativeReadableArrayAndroid?: () => boolean; + +useNativeTransformHelperAndroid?: () => boolean; +useNativeViewConfigsInBridgelessMode?: () => boolean; +useOptimizedEventBatchingOnAndroid?: () => boolean; +useRawPropsJsiValue?: () => boolean;