diff --git a/BUILD.gn b/BUILD.gn index d9b7d40b14449..86c63580228e3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -167,8 +167,10 @@ group("unittests") { public_deps = [] if (is_android) { - public_deps += - [ "//flutter/shell/platform/android:flutter_shell_native_unittests" ] + public_deps += [ + "//flutter/impeller/toolkit/android:unittests", + "//flutter/shell/platform/android:flutter_shell_native_unittests", + ] } if (is_ios) { diff --git a/ci/builders/linux_android_emulator.json b/ci/builders/linux_android_emulator.json index 918c8e6178b16..2340fe3921b64 100644 --- a/ci/builders/linux_android_emulator.json +++ b/ci/builders/linux_android_emulator.json @@ -29,6 +29,7 @@ "ninja": { "config": "android_emulator_debug_x64", "targets": [ + "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", "flutter/testing/scenario_app" ] @@ -120,6 +121,7 @@ "ninja": { "config": "android_emulator_debug_x86", "targets": [ + "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", "flutter/testing/scenario_app" ] diff --git a/ci/builders/linux_android_emulator_api_33.json b/ci/builders/linux_android_emulator_api_33.json index dfb70d49ce2d3..b955dd19e4d6f 100644 --- a/ci/builders/linux_android_emulator_api_33.json +++ b/ci/builders/linux_android_emulator_api_33.json @@ -29,6 +29,7 @@ "ninja": { "config": "android_debug_api33_x64", "targets": [ + "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", "flutter/testing/scenario_app" ] diff --git a/ci/builders/linux_android_emulator_skia.json b/ci/builders/linux_android_emulator_skia.json index 32fcbc96ac174..f2a5f97aec2c8 100644 --- a/ci/builders/linux_android_emulator_skia.json +++ b/ci/builders/linux_android_emulator_skia.json @@ -29,6 +29,7 @@ "ninja": { "config": "android_emulator_skia_debug_x64", "targets": [ + "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", "flutter/testing/scenario_app" ] diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 5635e1fd82483..dd2c355727afa 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -101,7 +101,6 @@ ../../../flutter/fml/message_loop_task_queues_unittests.cc ../../../flutter/fml/message_loop_unittests.cc ../../../flutter/fml/paths_unittests.cc -../../../flutter/fml/platform/android/ndk_helpers_unittests.cc ../../../flutter/fml/platform/darwin/cf_utils_unittests.mm ../../../flutter/fml/platform/darwin/scoped_nsobject_arc_unittests.mm ../../../flutter/fml/platform/darwin/scoped_nsobject_unittests.mm @@ -200,6 +199,8 @@ ../../../flutter/impeller/tessellator/dart/pubspec.lock ../../../flutter/impeller/tessellator/dart/pubspec.yaml ../../../flutter/impeller/tessellator/tessellator_unittests.cc +../../../flutter/impeller/toolkit/android/README.md +../../../flutter/impeller/toolkit/android/toolkit_android_unittests.cc ../../../flutter/impeller/tools/build_metal_library.py ../../../flutter/impeller/tools/check_licenses.py ../../../flutter/impeller/tools/malioc_cores.py diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c50c6ab586b4b..477bd20564f91 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -39364,8 +39364,6 @@ ORIGIN: ../../../flutter/fml/platform/android/jni_weak_ref.cc + ../../../flutter ORIGIN: ../../../flutter/fml/platform/android/jni_weak_ref.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/android/message_loop_android.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/android/message_loop_android.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/android/ndk_helpers.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/fml/platform/android/ndk_helpers.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/android/paths_android.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/android/paths_android.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/fml/platform/android/scoped_java_ref.cc + ../../../flutter/LICENSE @@ -40151,6 +40149,18 @@ ORIGIN: ../../../flutter/impeller/tessellator/c/tessellator.h + ../../../flutter ORIGIN: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/tessellator.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/tessellator/tessellator.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/choreographer.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/choreographer.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/hardware_buffer.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/hardware_buffer.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/native_window.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/native_window.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/proc_table.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/proc_table.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/surface_control.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/surface_control.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/egl/config.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/egl/config.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/toolkit/egl/context.cc + ../../../flutter/LICENSE @@ -40874,6 +40884,7 @@ ORIGIN: ../../../flutter/shell/platform/android/flutter_main.h + ../../../flutte ORIGIN: ../../../flutter/shell/platform/android/image_external_texture.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_gl.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_gl.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_vk.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_lru.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_lru.h + ../../../flutter/LICENSE @@ -42220,8 +42231,6 @@ FILE: ../../../flutter/fml/platform/android/jni_weak_ref.cc FILE: ../../../flutter/fml/platform/android/jni_weak_ref.h FILE: ../../../flutter/fml/platform/android/message_loop_android.cc FILE: ../../../flutter/fml/platform/android/message_loop_android.h -FILE: ../../../flutter/fml/platform/android/ndk_helpers.cc -FILE: ../../../flutter/fml/platform/android/ndk_helpers.h FILE: ../../../flutter/fml/platform/android/paths_android.cc FILE: ../../../flutter/fml/platform/android/paths_android.h FILE: ../../../flutter/fml/platform/android/scoped_java_ref.cc @@ -43008,6 +43017,18 @@ FILE: ../../../flutter/impeller/tessellator/c/tessellator.h FILE: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart FILE: ../../../flutter/impeller/tessellator/tessellator.cc FILE: ../../../flutter/impeller/tessellator/tessellator.h +FILE: ../../../flutter/impeller/toolkit/android/choreographer.cc +FILE: ../../../flutter/impeller/toolkit/android/choreographer.h +FILE: ../../../flutter/impeller/toolkit/android/hardware_buffer.cc +FILE: ../../../flutter/impeller/toolkit/android/hardware_buffer.h +FILE: ../../../flutter/impeller/toolkit/android/native_window.cc +FILE: ../../../flutter/impeller/toolkit/android/native_window.h +FILE: ../../../flutter/impeller/toolkit/android/proc_table.cc +FILE: ../../../flutter/impeller/toolkit/android/proc_table.h +FILE: ../../../flutter/impeller/toolkit/android/surface_control.cc +FILE: ../../../flutter/impeller/toolkit/android/surface_control.h +FILE: ../../../flutter/impeller/toolkit/android/surface_transaction.cc +FILE: ../../../flutter/impeller/toolkit/android/surface_transaction.h FILE: ../../../flutter/impeller/toolkit/egl/config.cc FILE: ../../../flutter/impeller/toolkit/egl/config.h FILE: ../../../flutter/impeller/toolkit/egl/context.cc diff --git a/fml/BUILD.gn b/fml/BUILD.gn index 2ca165612b0dd..ee43fc4b35584 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -185,8 +185,6 @@ source_set("fml") { "platform/android/jni_weak_ref.h", "platform/android/message_loop_android.cc", "platform/android/message_loop_android.h", - "platform/android/ndk_helpers.cc", - "platform/android/ndk_helpers.h", "platform/android/paths_android.cc", "platform/android/paths_android.h", "platform/android/scoped_java_ref.cc", diff --git a/fml/platform/android/ndk_helpers.cc b/fml/platform/android/ndk_helpers.cc deleted file mode 100644 index 1fcc49b0146b9..0000000000000 --- a/fml/platform/android/ndk_helpers.cc +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "fml/platform/android/ndk_helpers.h" - -#include "fml/logging.h" -#include "fml/native_library.h" - -#include -#include - -namespace flutter { - -namespace { - -#define DECLARE_TYPES(ret, name, args) \ - typedef ret(*fp_##name) args; \ - ret(*_##name) args = nullptr - -DECLARE_TYPES(int, - AHardwareBuffer_allocate, - (const AHardwareBuffer_Desc* desc, AHardwareBuffer** outBuffer)); -DECLARE_TYPES(int, - AHardwareBuffer_isSupported, - (const AHardwareBuffer_Desc* desc)); -DECLARE_TYPES(AHardwareBuffer*, - AHardwareBuffer_fromHardwareBuffer, - (JNIEnv * env, jobject hardwareBufferObj)); -DECLARE_TYPES(void, AHardwareBuffer_release, (AHardwareBuffer * buffer)); -DECLARE_TYPES(void, - AHardwareBuffer_describe, - (AHardwareBuffer * buffer, AHardwareBuffer_Desc* desc)); -DECLARE_TYPES(int, - AHardwareBuffer_getId, - (AHardwareBuffer * buffer, uint64_t* outId)); - -DECLARE_TYPES(bool, ATrace_isEnabled, (void)); - -DECLARE_TYPES(ASurfaceControl*, - ASurfaceControl_createFromWindow, - (ANativeWindow * parent, const char* debug_name)); -DECLARE_TYPES(void, - ASurfaceControl_release, - (ASurfaceControl * surface_control)); -DECLARE_TYPES(ASurfaceTransaction*, ASurfaceTransaction_create, (void)); -DECLARE_TYPES(void, - ASurfaceTransaction_delete, - (ASurfaceTransaction * surface_transaction)); -DECLARE_TYPES(void, - ASurfaceTransaction_apply, - (ASurfaceTransaction * surface_transaction)); -DECLARE_TYPES(void, - ASurfaceTransaction_setBuffer, - (ASurfaceTransaction * transaction, - ASurfaceControl* surface_control, - AHardwareBuffer* buffer, - int acquire_fence_fd)); - -DECLARE_TYPES(AChoreographer*, AChoreographer_getInstance, (void)); -DECLARE_TYPES(void, - AChoreographer_postFrameCallback, - (AChoreographer * choreographer, - AChoreographer_frameCallback callbackk, - void* data)); -DECLARE_TYPES(void, - AChoreographer_postFrameCallback64, - (AChoreographer * choreographer, - AChoreographer_frameCallback64 callbackk, - void* data)); - -DECLARE_TYPES(EGLClientBuffer, - eglGetNativeClientBufferANDROID, - (AHardwareBuffer * buffer)); - -#undef DECLARE_TYPES - -std::once_flag init_once; - -void InitOnceCallback() { - static fml::RefPtr android = - fml::NativeLibrary::Create("libandroid.so"); - FML_CHECK(android.get() != nullptr); - static fml::RefPtr egl = - fml::NativeLibrary::Create("libEGL.so"); - FML_CHECK(egl.get() != nullptr); - -#define LOOKUP(lib, func) \ - _##func = lib->ResolveFunction(#func).value_or(nullptr) - - LOOKUP(egl, eglGetNativeClientBufferANDROID); - - LOOKUP(android, AHardwareBuffer_fromHardwareBuffer); - LOOKUP(android, AHardwareBuffer_release); - LOOKUP(android, AHardwareBuffer_getId); - LOOKUP(android, AHardwareBuffer_describe); - LOOKUP(android, AHardwareBuffer_allocate); - LOOKUP(android, AHardwareBuffer_isSupported); - LOOKUP(android, ATrace_isEnabled); - LOOKUP(android, AChoreographer_getInstance); - if (_AChoreographer_getInstance) { - LOOKUP(android, AChoreographer_postFrameCallback64); -// See discussion at -// https://github.com/flutter/engine/pull/31859#discussion_r822072987 -// This method is not suitable for Flutter's use cases on 32 bit architectures, -// and we should fall back to the Java based Choreographer. -#if FML_ARCH_CPU_64_BITS - if (!_AChoreographer_postFrameCallback64) { - LOOKUP(android, AChoreographer_postFrameCallback); - } -#endif - } - - LOOKUP(android, ASurfaceControl_createFromWindow); - LOOKUP(android, ASurfaceControl_release); - LOOKUP(android, ASurfaceTransaction_apply); - LOOKUP(android, ASurfaceTransaction_create); - LOOKUP(android, ASurfaceTransaction_delete); - LOOKUP(android, ASurfaceTransaction_setBuffer); -#undef LOOKUP -} - -} // namespace - -void NDKHelpers::Init() { - std::call_once(init_once, InitOnceCallback); -} - -bool NDKHelpers::ATrace_isEnabled() { - if (_ATrace_isEnabled) { - return _ATrace_isEnabled(); - } - return false; -} - -ChoreographerSupportStatus NDKHelpers::ChoreographerSupported() { - if (_AChoreographer_postFrameCallback64) { - return ChoreographerSupportStatus::kSupported64; - } - if (_AChoreographer_postFrameCallback) { - return ChoreographerSupportStatus::kSupported32; - } - return ChoreographerSupportStatus::kUnsupported; -} - -AChoreographer* NDKHelpers::AChoreographer_getInstance() { - FML_CHECK(_AChoreographer_getInstance); - return _AChoreographer_getInstance(); -} - -void NDKHelpers::AChoreographer_postFrameCallback( - AChoreographer* choreographer, - AChoreographer_frameCallback callback, - void* data) { - FML_CHECK(_AChoreographer_postFrameCallback); - return _AChoreographer_postFrameCallback(choreographer, callback, data); -} - -void NDKHelpers::AChoreographer_postFrameCallback64( - AChoreographer* choreographer, - AChoreographer_frameCallback64 callback, - void* data) { - FML_CHECK(_AChoreographer_postFrameCallback64); - return _AChoreographer_postFrameCallback64(choreographer, callback, data); -} - -bool NDKHelpers::HardwareBufferSupported() { - const bool r = _AHardwareBuffer_fromHardwareBuffer != nullptr; - return r; -} - -AHardwareBuffer* NDKHelpers::AHardwareBuffer_fromHardwareBuffer( - JNIEnv* env, - jobject hardwareBufferObj) { - FML_CHECK(_AHardwareBuffer_fromHardwareBuffer != nullptr); - return _AHardwareBuffer_fromHardwareBuffer(env, hardwareBufferObj); -} - -void NDKHelpers::AHardwareBuffer_release(AHardwareBuffer* buffer) { - FML_CHECK(_AHardwareBuffer_release != nullptr); - _AHardwareBuffer_release(buffer); -} - -void NDKHelpers::AHardwareBuffer_describe(AHardwareBuffer* buffer, - AHardwareBuffer_Desc* desc) { - FML_CHECK(_AHardwareBuffer_describe != nullptr); - _AHardwareBuffer_describe(buffer, desc); -} - -std::optional NDKHelpers::AHardwareBuffer_getId( - AHardwareBuffer* buffer) { - if (_AHardwareBuffer_getId == nullptr) { - return std::nullopt; - } - HardwareBufferKey outId; - int result = _AHardwareBuffer_getId(buffer, &outId); - if (result == 0) { - return outId; - } - return std::nullopt; -} - -EGLClientBuffer NDKHelpers::eglGetNativeClientBufferANDROID( - AHardwareBuffer* buffer) { - FML_CHECK(_eglGetNativeClientBufferANDROID != nullptr); - return _eglGetNativeClientBufferANDROID(buffer); -} - -bool NDKHelpers::SurfaceControlAndTransactionSupported() { - return _ASurfaceControl_createFromWindow && _ASurfaceControl_release && - _ASurfaceTransaction_create && _ASurfaceTransaction_apply && - _ASurfaceTransaction_delete && _ASurfaceTransaction_setBuffer; -} - -ASurfaceControl* NDKHelpers::ASurfaceControl_createFromWindow( - ANativeWindow* parent, - const char* debug_name) { - FML_CHECK(_ASurfaceControl_createFromWindow); - return _ASurfaceControl_createFromWindow(parent, debug_name); -} - -void NDKHelpers::ASurfaceControl_release(ASurfaceControl* surface_control) { - FML_CHECK(_ASurfaceControl_release); - return _ASurfaceControl_release(surface_control); -} - -ASurfaceTransaction* NDKHelpers::ASurfaceTransaction_create() { - FML_CHECK(_ASurfaceTransaction_create); - return _ASurfaceTransaction_create(); -} - -void NDKHelpers::ASurfaceTransaction_delete( - ASurfaceTransaction* surface_transaction) { - FML_CHECK(_ASurfaceTransaction_delete); - _ASurfaceTransaction_delete(surface_transaction); -} - -void NDKHelpers::ASurfaceTransaction_apply( - ASurfaceTransaction* surface_transaction) { - FML_CHECK(_ASurfaceTransaction_apply); - _ASurfaceTransaction_apply(surface_transaction); -} - -void NDKHelpers::ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction, - ASurfaceControl* surface_control, - AHardwareBuffer* buffer, - int acquire_fence_fd) { - FML_CHECK(_ASurfaceTransaction_setBuffer); - _ASurfaceTransaction_setBuffer(transaction, surface_control, buffer, - acquire_fence_fd); -} - -int NDKHelpers::AHardwareBuffer_isSupported(const AHardwareBuffer_Desc* desc) { - FML_CHECK(_AHardwareBuffer_isSupported); - return _AHardwareBuffer_isSupported(desc); -} - -int NDKHelpers::AHardwareBuffer_allocate(const AHardwareBuffer_Desc* desc, - AHardwareBuffer** outBuffer) { - FML_CHECK(_AHardwareBuffer_allocate); - return _AHardwareBuffer_allocate(desc, outBuffer); -} - -} // namespace flutter diff --git a/fml/platform/android/ndk_helpers.h b/fml/platform/android/ndk_helpers.h deleted file mode 100644 index 9299776e3e691..0000000000000 --- a/fml/platform/android/ndk_helpers.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_FML_PLATFORM_ANDROID_NDK_HELPERS_H_ -#define FLUTTER_FML_PLATFORM_ANDROID_NDK_HELPERS_H_ - -#include -#include -#include -#include -#include -#include -#include - -namespace flutter { - -using HardwareBufferKey = uint64_t; - -enum class ChoreographerSupportStatus { - // Unavailable, API level < 24. - kUnsupported, - // Available, but only with postFrameCallback. - kSupported32, - // Available, but only with postFrameCallback64. - kSupported64, -}; - -// A collection of NDK functions that are available depending on the version of -// the Android SDK we are linked with at runtime. -class NDKHelpers { - public: - // Safe to call multiple times. - // Normally called from JNI_OnLoad. - static void Init(); - - // API Version 23 - static bool ATrace_isEnabled(); - - // API Version 24 - static ChoreographerSupportStatus ChoreographerSupported(); - static AChoreographer* _Nullable AChoreographer_getInstance(); - // Deprecated in 29, available since 24. - static void AChoreographer_postFrameCallback( - AChoreographer* _Nonnull choreographer, - AChoreographer_frameCallback _Nonnull callback, - void* _Nullable data); - - // API Version 26 - static bool HardwareBufferSupported(); - static AHardwareBuffer* _Nonnull AHardwareBuffer_fromHardwareBuffer( - JNIEnv* _Nonnull env, - jobject _Nonnull hardwareBufferObj); - static void AHardwareBuffer_release(AHardwareBuffer* _Nonnull buffer); - static void AHardwareBuffer_describe(AHardwareBuffer* _Nonnull buffer, - AHardwareBuffer_Desc* _Nullable desc); - static int AHardwareBuffer_allocate( - const AHardwareBuffer_Desc* _Nonnull desc, - AHardwareBuffer* _Nullable* _Nullable outBuffer); - static EGLClientBuffer _Nonnull eglGetNativeClientBufferANDROID( - AHardwareBuffer* _Nonnull buffer); - - // API Version 29 - static int AHardwareBuffer_isSupported( - const AHardwareBuffer_Desc* _Nonnull desc); - - static void AChoreographer_postFrameCallback64( - AChoreographer* _Nonnull choreographer, - AChoreographer_frameCallback64 _Nonnull callback, - void* _Nullable data); - - static bool SurfaceControlAndTransactionSupported(); - - static ASurfaceControl* _Nonnull ASurfaceControl_createFromWindow( - ANativeWindow* _Nonnull parent, - const char* _Nullable debug_name); - static void ASurfaceControl_release( - ASurfaceControl* _Nonnull surface_control); - - static ASurfaceTransaction* _Nonnull ASurfaceTransaction_create(); - static void ASurfaceTransaction_delete( - ASurfaceTransaction* _Nonnull surface_transaction); - static void ASurfaceTransaction_apply( - ASurfaceTransaction* _Nonnull surface_transaction); - static void ASurfaceTransaction_setBuffer( - ASurfaceTransaction* _Nonnull transaction, - ASurfaceControl* _Nonnull surface_control, - AHardwareBuffer* _Nonnull buffer, - int acquire_fence_fd); - - // API Version 31 - - // Returns std::nullopt on API version 26 - 30. - static std::optional AHardwareBuffer_getId( - AHardwareBuffer* _Nonnull buffer); -}; - -} // namespace flutter - -#endif // FLUTTER_FML_PLATFORM_ANDROID_NDK_HELPERS_H_ diff --git a/fml/platform/android/ndk_helpers_unittests.cc b/fml/platform/android/ndk_helpers_unittests.cc deleted file mode 100644 index dfcd528d9072a..0000000000000 --- a/fml/platform/android/ndk_helpers_unittests.cc +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "fml/message_loop.h" -#include "fml/platform/android/ndk_helpers.h" - -#include "gtest/gtest.h" - -namespace flutter { -namespace testing { -namespace android { - -class NdkHelpersTest : public ::testing::Test { - public: - void SetUp() override { NDKHelpers::Init(); } - - static void OnVsync(int64_t frame_nanos, void* data) {} - static void OnVsync32( - long frame_nanos, // NOLINT - compat for deprecated call - void* data) {} -}; - -TEST_F(NdkHelpersTest, ATrace) { - ASSERT_GT(android_get_device_api_level(), 22); - EXPECT_FALSE(NDKHelpers::ATrace_isEnabled()); -} - -#if FML_ARCH_CPU_64_BITS -TEST_F(NdkHelpersTest, AChoreographer32) { - if (android_get_device_api_level() >= 29) { - GTEST_SKIP() << "This test is for less than API 29."; - } - - EXPECT_EQ(NDKHelpers::ChoreographerSupported(), - ChoreographerSupportStatus::kSupported32); - - EXPECT_FALSE(NDKHelpers::AChoreographer_getInstance()); - - fml::MessageLoop::EnsureInitializedForCurrentThread(); - - EXPECT_TRUE(NDKHelpers::AChoreographer_getInstance()); - - NDKHelpers::AChoreographer_postFrameCallback( - NDKHelpers::AChoreographer_getInstance(), &OnVsync32, nullptr); -} -#else -TEST_F(NdkHelpersTest, AChoreographer32NotSupported) { - if (android_get_device_api_level() >= 29) { - GTEST_SKIP() << "This test is for less than API 29."; - } - - // The 32 bit framecallback on 32 bit architectures does not deliver - // sufficient resolution. See - // https://github.com/flutter/engine/pull/31859#discussion_r822072987 - EXPECT_EQ(NDKHelpers::ChoreographerSupported(), - ChoreographerSupportStatus::kUnsupported); -} -#endif // FML_ARCH_CPU_64_BITS - -TEST_F(NdkHelpersTest, AChoreographer64) { - if (android_get_device_api_level() < 29) { - GTEST_SKIP() << "This test is for API 29 and above."; - } - - EXPECT_EQ(NDKHelpers::ChoreographerSupported(), - ChoreographerSupportStatus::kSupported64); - - EXPECT_FALSE(NDKHelpers::AChoreographer_getInstance()); - - fml::MessageLoop::EnsureInitializedForCurrentThread(); - - EXPECT_TRUE(NDKHelpers::AChoreographer_getInstance()); - - NDKHelpers::AChoreographer_postFrameCallback64( - NDKHelpers::AChoreographer_getInstance(), &OnVsync, nullptr); -} - -TEST_F(NdkHelpersTest, HardwareBuffer) { - if (android_get_device_api_level() < 26) { - GTEST_SKIP() << "Test requires at least API 26."; - } - - ASSERT_TRUE(NDKHelpers::HardwareBufferSupported()); - - AHardwareBuffer_Desc desc{ - .width = 4, - .height = 4, - .layers = 1, - .format = AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, - }; - if (android_get_device_api_level() >= 29) { - EXPECT_TRUE(NDKHelpers::AHardwareBuffer_isSupported(&desc)); - } - - AHardwareBuffer* buffer = nullptr; - // AHardwareBuffer_allocate returns 0 on success. - EXPECT_EQ(NDKHelpers::AHardwareBuffer_allocate(&desc, &buffer), 0); - EXPECT_TRUE(buffer); - - AHardwareBuffer_Desc out_desc = {}; - NDKHelpers::AHardwareBuffer_describe(buffer, &out_desc); - EXPECT_EQ(desc.width, out_desc.width); - EXPECT_EQ(desc.height, out_desc.height); - EXPECT_EQ(desc.layers, out_desc.layers); - EXPECT_EQ(desc.format, out_desc.format); - - auto id = NDKHelpers::AHardwareBuffer_getId(buffer); - if (android_get_device_api_level() >= 31) { - EXPECT_TRUE(id.has_value()); - } else { - EXPECT_FALSE(id.has_value()); - } - - NDKHelpers::AHardwareBuffer_release(buffer); -} - -TEST_F(NdkHelpersTest, SurfaceTransaction) { - if (android_get_device_api_level() < 29) { - GTEST_SKIP() << "Test requires at least API 29."; - } - EXPECT_TRUE(NDKHelpers::SurfaceControlAndTransactionSupported()); - - // Need ANativeWindow to create ASurfaceControl and set a buffer to the - // transaction. Just create/apply/delete as a smoke test. - - ASurfaceTransaction* transaction = NDKHelpers::ASurfaceTransaction_create(); - EXPECT_TRUE(transaction); - NDKHelpers::ASurfaceTransaction_apply(transaction); - NDKHelpers::ASurfaceTransaction_delete(transaction); -} - -} // namespace android -} // namespace testing -} // namespace flutter diff --git a/impeller/toolkit/android/BUILD.gn b/impeller/toolkit/android/BUILD.gn new file mode 100644 index 0000000000000..fd0dde36bd9bf --- /dev/null +++ b/impeller/toolkit/android/BUILD.gn @@ -0,0 +1,54 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../tools/impeller.gni") + +config("public_android_config") { + defines = [ "__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__" ] +} + +impeller_component("android") { + sources = [ + "choreographer.cc", + "choreographer.h", + "hardware_buffer.cc", + "hardware_buffer.h", + "native_window.cc", + "native_window.h", + "proc_table.cc", + "proc_table.h", + "surface_control.cc", + "surface_control.h", + "surface_transaction.cc", + "surface_transaction.h", + ] + + public_deps = [ + "../../base", + "../../geometry", + "//flutter/fml", + ] + + public_configs = [ ":public_android_config" ] +} + +test_fixtures("unittests_fixtures") { + fixtures = [] +} + +executable("unittests") { + assert(is_android) + + testonly = true + + output_name = "impeller_toolkit_android_unittests" + + sources = [ "toolkit_android_unittests.cc" ] + + deps = [ + ":android", + ":unittests_fixtures", + "//flutter/testing", + ] +} diff --git a/impeller/toolkit/android/README.md b/impeller/toolkit/android/README.md new file mode 100644 index 0000000000000..b52d8f6ea8f7f --- /dev/null +++ b/impeller/toolkit/android/README.md @@ -0,0 +1,7 @@ +Android Toolkit +=============== + +Type-safe managed wrappers around Android objects vended by the NDK. Does not +require linking to libandroid.so. The symbols are resolved via dynamic runtime +lookup so that the toolkit can be built with an older NDK but still run on +modern Android versions and use the latest features. diff --git a/impeller/toolkit/android/choreographer.cc b/impeller/toolkit/android/choreographer.cc new file mode 100644 index 0000000000000..f723e3e62fc07 --- /dev/null +++ b/impeller/toolkit/android/choreographer.cc @@ -0,0 +1,84 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/impeller/toolkit/android/choreographer.h" + +#include "flutter/fml/message_loop.h" + +namespace impeller::android { + +Choreographer& Choreographer::GetInstance() { + static thread_local Choreographer tChoreographer; + return tChoreographer; +} + +Choreographer::Choreographer() { + if (!IsAvailableOnPlatform()) { + return; + } + + // We need a message loop on the current thread for the choreographer to + // schedule callbacks for us on. + fml::MessageLoop::EnsureInitializedForCurrentThread(); + instance_ = GetProcTable().AChoreographer_getInstance(); +} + +Choreographer::~Choreographer() = default; + +bool Choreographer::IsValid() const { + return !!instance_; +} + +static Choreographer::FrameTimePoint ClockMonotonicNanosToFrameTimePoint( + int64_t p_nanos) { + return Choreographer::FrameTimePoint{std::chrono::nanoseconds(p_nanos)}; +} + +bool Choreographer::PostFrameCallback(FrameCallback callback) const { + if (!callback || !IsValid()) { + return false; + } + + struct InFlightData { + FrameCallback callback; + }; + + auto data = std::make_unique(); + data->callback = std::move(callback); + + const auto& table = GetProcTable(); + if (table.AChoreographer_postFrameCallback64) { + table.AChoreographer_postFrameCallback64( + const_cast(instance_), + [](int64_t nanos, void* p_data) { + auto data = reinterpret_cast(p_data); + data->callback(ClockMonotonicNanosToFrameTimePoint(nanos)); + delete data; + }, + data.release()); + return true; + } else if (table.AChoreographer_postFrameCallback) { + table.AChoreographer_postFrameCallback( + const_cast(instance_), + [](long /*NOLINT*/ nanos, void* p_data) { + auto data = reinterpret_cast(p_data); + data->callback(ClockMonotonicNanosToFrameTimePoint(nanos)); + delete data; + }, + data.release()); + return true; + } + + // The validity check should have tripped by now. + FML_UNREACHABLE(); + return false; +} + +bool Choreographer::IsAvailableOnPlatform() { + return GetProcTable().AChoreographer_getInstance || + GetProcTable().AChoreographer_postFrameCallback64 || + GetProcTable().AChoreographer_postFrameCallback; +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/choreographer.h b/impeller/toolkit/android/choreographer.h new file mode 100644 index 0000000000000..a4b781df8535f --- /dev/null +++ b/impeller/toolkit/android/choreographer.h @@ -0,0 +1,84 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_CHOREOGRAPHER_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_CHOREOGRAPHER_H_ + +#include "impeller/toolkit/android/proc_table.h" + +#include +#include + +namespace impeller::android { + +//------------------------------------------------------------------------------ +/// @brief This class describes access to the choreographer instance for +/// the current thread. Choreographers are only available on API +/// levels above 24. On levels below 24, an invalid choreographer +/// will be returned. +/// +/// Since choreographer need an event loop on the current thread, +/// one will be setup if it doesn't already exist. +/// +class Choreographer { + public: + static bool IsAvailableOnPlatform(); + + //---------------------------------------------------------------------------- + /// @brief Create or get the thread local instance of a choreographer. A + /// message loop will be setup on the calling thread if none + /// exists. + /// + /// @warning Choreographers are only available on API levels 24 and above. + /// Below this level, this will return an invalid instance. + /// Availability can also be checked via the + /// `IsAvailableOnPlatform` call. + /// + /// @return The thread local choreographer instance. If none can be setup, + /// an invalid object reference will be returned. See `IsValid`. + /// + static Choreographer& GetInstance(); + + ~Choreographer(); + + Choreographer(const Choreographer&) = delete; + + Choreographer& operator=(const Choreographer&) = delete; + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// A monotonic system clock. + /// + using FrameClock = std::chrono::steady_clock; + + //---------------------------------------------------------------------------- + /// A timepoint on a monotonic system clock. + /// + using FrameTimePoint = std::chrono::time_point; + using FrameCallback = std::function; + + //---------------------------------------------------------------------------- + /// @brief Posts a frame callback. The time that the frame is being + /// rendered will be available in the callback as an argument. + /// Multiple frame callbacks within the same frame interval will + /// receive the same argument. + /// + /// @param[in] callback The callback + /// + /// @return `true` if the frame callback could be posted. This may return + /// `false` if choreographers are not available on the platform. + /// See `IsAvailableOnPlatform`. + /// + bool PostFrameCallback(FrameCallback callback) const; + + private: + AChoreographer* instance_ = nullptr; + + explicit Choreographer(); +}; + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_CHOREOGRAPHER_H_ diff --git a/impeller/toolkit/android/hardware_buffer.cc b/impeller/toolkit/android/hardware_buffer.cc new file mode 100644 index 0000000000000..bfa52a2063cb6 --- /dev/null +++ b/impeller/toolkit/android/hardware_buffer.cc @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/toolkit/android/hardware_buffer.h" + +#include "impeller/base/validation.h" + +namespace impeller::android { + +static AHardwareBuffer_Format ToAHardwareBufferFormat( + HardwareBufferFormat format) { + switch (format) { + case HardwareBufferFormat::kR8G8B8A8UNormInt: + return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + } + FML_UNREACHABLE(); +} + +static AHardwareBuffer_Desc ToAHardwareBufferDesc( + const HardwareBufferDescriptor& desc) { + AHardwareBuffer_Desc ahb_desc = {}; + ahb_desc.width = desc.size.width; + ahb_desc.height = desc.size.height; + ahb_desc.format = ToAHardwareBufferFormat(desc.format); + ahb_desc.layers = 1u; + if (desc.usage & static_cast( + HardwareBufferUsageFlags::kFrameBufferAttachment)) { + ahb_desc.usage |= AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; + } + if (desc.usage & static_cast( + HardwareBufferUsageFlags::kCompositorOverlay)) { + ahb_desc.usage |= AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY; + } + if (desc.usage & static_cast( + HardwareBufferUsageFlags::kSampledImage)) { + ahb_desc.usage |= AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE; + } + return ahb_desc; +} + +bool HardwareBufferDescriptor::IsAllocatable() const { + const auto desc = ToAHardwareBufferDesc(*this); + return GetProcTable().AHardwareBuffer_isSupported(&desc) != 0u; +} + +HardwareBuffer::HardwareBuffer(HardwareBufferDescriptor descriptor) + : descriptor_(descriptor), + android_descriptor_(ToAHardwareBufferDesc(descriptor_)) { + if (!descriptor_.IsAllocatable()) { + VALIDATION_LOG << "The hardware buffer descriptor is not allocatable."; + return; + } + const auto& proc_table = GetProcTable(); + + AHardwareBuffer* buffer = nullptr; + if (auto result = + proc_table.AHardwareBuffer_allocate(&android_descriptor_, &buffer); + result != 0 || buffer == nullptr) { + VALIDATION_LOG << "Could not allocate hardware buffer. Error: " << result; + return; + } + buffer_.reset(buffer); + is_valid_ = true; +} + +HardwareBuffer::~HardwareBuffer() = default; + +bool HardwareBuffer::IsValid() const { + return is_valid_; +} + +AHardwareBuffer* HardwareBuffer::GetHandle() const { + return buffer_.get(); +} + +HardwareBufferDescriptor HardwareBufferDescriptor::MakeForSwapchainImage( + const ISize& size) { + HardwareBufferDescriptor desc; + desc.format = HardwareBufferFormat::kR8G8B8A8UNormInt; + // Zero sized hardware buffers cannot be allocated. + desc.size = size.Max(ISize{1u, 1u}); + desc.usage = + static_cast( + HardwareBufferUsageFlags::kFrameBufferAttachment) | + static_cast( + HardwareBufferUsageFlags::kCompositorOverlay) | + static_cast(HardwareBufferUsageFlags::kSampledImage); + return desc; +} + +const HardwareBufferDescriptor& HardwareBuffer::GetDescriptor() const { + return descriptor_; +} + +const AHardwareBuffer_Desc& HardwareBuffer::GetAndroidDescriptor() const { + return android_descriptor_; +} + +bool HardwareBuffer::IsAvailableOnPlatform() { + return GetProcTable().IsValid() && + GetProcTable().AHardwareBuffer_allocate.IsAvailable(); +} + +std::optional HardwareBuffer::GetSystemUniqueID() const { + return GetSystemUniqueID(GetHandle()); +} + +std::optional HardwareBuffer::GetSystemUniqueID( + AHardwareBuffer* buffer) { + if (!GetProcTable().AHardwareBuffer_getId) { + return false; + } + uint64_t out_id = 0u; + if (GetProcTable().AHardwareBuffer_getId(buffer, &out_id) != 0) { + return std::nullopt; + } + return out_id; +} + +std::optional HardwareBuffer::Describe( + AHardwareBuffer* buffer) { + if (!buffer || !GetProcTable().AHardwareBuffer_describe) { + return std::nullopt; + } + AHardwareBuffer_Desc desc = {}; + GetProcTable().AHardwareBuffer_describe(buffer, &desc); + return desc; +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/hardware_buffer.h b/impeller/toolkit/android/hardware_buffer.h new file mode 100644 index 0000000000000..02b99b1013674 --- /dev/null +++ b/impeller/toolkit/android/hardware_buffer.h @@ -0,0 +1,149 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_HARDWARE_BUFFER_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_HARDWARE_BUFFER_H_ + +#include + +#include "flutter/fml/unique_object.h" +#include "impeller/geometry/size.h" +#include "impeller/toolkit/android/proc_table.h" + +namespace impeller::android { + +enum class HardwareBufferFormat { + //---------------------------------------------------------------------------- + /// This format is guaranteed to be supported on all versions of Android. This + /// format can also be converted to an Impeller and Vulkan format. + /// + /// @see Vulkan Format: VK_FORMAT_R8G8B8A8_UNORM + /// @see OpenGL ES Format: GL_RGBA8 + /// + /// Why have many format when one format do trick? + /// + kR8G8B8A8UNormInt, +}; + +using HardwareBufferUsage = uint8_t; + +enum class HardwareBufferUsageFlags : HardwareBufferUsage { + kFrameBufferAttachment = 1u << 0u, + kCompositorOverlay = 1u << 1u, + kSampledImage = 1u << 2u, +}; + +//------------------------------------------------------------------------------ +/// @brief A descriptor use to specify hardware buffer allocations. +/// +struct HardwareBufferDescriptor { + HardwareBufferFormat format = HardwareBufferFormat::kR8G8B8A8UNormInt; + ISize size; + HardwareBufferUsage usage = 0u; + + //---------------------------------------------------------------------------- + /// @brief Create a descriptor of the given size that is suitable for use + /// as a swapchain image. + /// + /// @warning Descriptors of zero size are not allocatable. The next best + /// valid size is picked. So make sure to check the actual size of + /// the descriptor after this call is made to determine the size + /// of the allocated hardware buffer. + /// + /// @param[in] size The size. See the restrictions about valid sizes above. + /// + /// @return The hardware buffer descriptor. + /// + static HardwareBufferDescriptor MakeForSwapchainImage(const ISize& size); + + //---------------------------------------------------------------------------- + /// @brief If hardware buffers can be created using this descriptor. + /// Allocatable descriptors may still cause failing allocations in + /// case of resource exhaustion. + /// + /// @return `true` if allocatable (unless resource exhaustion). + /// + bool IsAllocatable() const; + + constexpr bool operator==(const HardwareBufferDescriptor& o) const { + return format == o.format && size == o.size && usage == o.usage; + } + + constexpr bool operator!=(const HardwareBufferDescriptor& o) const { + return !(*this == o); + } +}; + +//------------------------------------------------------------------------------ +/// @brief A wrapper for AHardwareBuffer +/// https://developer.android.com/ndk/reference/group/a-hardware-buffer +/// +/// This wrapper creates and owns a handle to a managed hardware +/// buffer. That is, there is no ability to take a reference to an +/// externally created hardware buffer. +/// +/// This wrapper is only available on Android API 29 and above. +/// +class HardwareBuffer { + public: + static bool IsAvailableOnPlatform(); + + explicit HardwareBuffer(HardwareBufferDescriptor descriptor); + + ~HardwareBuffer(); + + HardwareBuffer(const HardwareBuffer&) = delete; + + HardwareBuffer& operator=(const HardwareBuffer&) = delete; + + bool IsValid() const; + + AHardwareBuffer* GetHandle() const; + + const HardwareBufferDescriptor& GetDescriptor() const; + + const AHardwareBuffer_Desc& GetAndroidDescriptor() const; + + static std::optional Describe(AHardwareBuffer* buffer); + + //---------------------------------------------------------------------------- + /// @brief Get the system wide unique ID of the hardware buffer if + /// possible. This is only available on Android API 31 and above. + /// Within the process, the handle are unique. + /// + /// @return The system unique id if one can be obtained. + /// + std::optional GetSystemUniqueID() const; + + //---------------------------------------------------------------------------- + /// @brief Get the system wide unique ID of the hardware buffer if + /// possible. This is only available on Android API 31 and above. + /// Within the process, the handle are unique. + /// + /// @return The system unique id if one can be obtained. + /// + static std::optional GetSystemUniqueID(AHardwareBuffer* buffer); + + private: + struct UniqueAHardwareBufferTraits { + static AHardwareBuffer* InvalidValue() { return nullptr; } + + static bool IsValid(AHardwareBuffer* value) { + return value != InvalidValue(); + } + + static void Free(AHardwareBuffer* value) { + GetProcTable().AHardwareBuffer_release(value); + } + }; + + const HardwareBufferDescriptor descriptor_; + const AHardwareBuffer_Desc android_descriptor_; + fml::UniqueObject buffer_; + bool is_valid_ = false; +}; + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_HARDWARE_BUFFER_H_ diff --git a/impeller/toolkit/android/native_window.cc b/impeller/toolkit/android/native_window.cc new file mode 100644 index 0000000000000..663a482f624a3 --- /dev/null +++ b/impeller/toolkit/android/native_window.cc @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/toolkit/android/native_window.h" + +namespace impeller::android { + +NativeWindow::NativeWindow(ANativeWindow* window) : window_(window) { + if (window_.get()) { + GetProcTable().ANativeWindow_acquire(window_.get()); + } +} + +NativeWindow::~NativeWindow() = default; + +bool NativeWindow::IsValid() const { + return window_.is_valid(); +} + +ISize NativeWindow::GetSize() const { + if (!IsValid()) { + return {}; + } + const int32_t width = ANativeWindow_getWidth(window_.get()); + const int32_t height = ANativeWindow_getHeight(window_.get()); + return ISize::MakeWH(std::max(width, 0), std::max(height, 0)); +} + +ANativeWindow* NativeWindow::GetHandle() const { + return window_.get(); +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/native_window.h b/impeller/toolkit/android/native_window.h new file mode 100644 index 0000000000000..8af6cfb4ba5a6 --- /dev/null +++ b/impeller/toolkit/android/native_window.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_NATIVE_WINDOW_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_NATIVE_WINDOW_H_ + +#include "flutter/fml/unique_object.h" +#include "impeller/geometry/size.h" +#include "impeller/toolkit/android/proc_table.h" + +namespace impeller::android { + +//------------------------------------------------------------------------------ +/// @brief A wrapper for ANativeWindow +/// https://developer.android.com/ndk/reference/group/a-native-window +/// +/// This wrapper is only available on Android. +/// +class NativeWindow { + public: + explicit NativeWindow(ANativeWindow* window); + + ~NativeWindow(); + + NativeWindow(const NativeWindow&) = delete; + + NativeWindow& operator=(const NativeWindow&) = delete; + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @return The current size of the native window. + /// + ISize GetSize() const; + + ANativeWindow* GetHandle() const; + + private: + struct UniqueANativeWindowTraits { + static ANativeWindow* InvalidValue() { return nullptr; } + + static bool IsValid(ANativeWindow* value) { + return value != InvalidValue(); + } + + static void Free(ANativeWindow* value) { + GetProcTable().ANativeWindow_release(value); + } + }; + + fml::UniqueObject window_; +}; + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_NATIVE_WINDOW_H_ diff --git a/impeller/toolkit/android/proc_table.cc b/impeller/toolkit/android/proc_table.cc new file mode 100644 index 0000000000000..71e6ce5c4d14d --- /dev/null +++ b/impeller/toolkit/android/proc_table.cc @@ -0,0 +1,68 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/impeller/toolkit/android/proc_table.h" + +#include "flutter/fml/build_config.h" +#include "impeller/base/validation.h" + +namespace impeller::android { + +const ProcTable& GetProcTable() { + static ProcTable gProcTable; + return gProcTable; +} + +template +void ResolveAndroidProc( + AndroidProc& proc, + const std::vector>& libs) { + for (const auto& lib : libs) { + proc.proc = lib->ResolveFunction(proc.proc_name).value_or(nullptr); + if (proc.proc) { + break; + } + } +} + +ProcTable::ProcTable() { + auto lib_android = fml::NativeLibrary::Create("libandroid.so"); + auto lib_egl = fml::NativeLibrary::Create("libEGL.so"); + + if (!lib_android || !lib_egl) { + VALIDATION_LOG << "Could not open Android libraries."; + return; + } + + libraries_.push_back(std::move(lib_android)); + libraries_.push_back(std::move(lib_egl)); + +#define RESOLVE_PROC(proc, api) ResolveAndroidProc(proc, libraries_); + FOR_EACH_ANDROID_PROC(RESOLVE_PROC); +#undef RESOLVE_PROC + + if (AChoreographer_postFrameCallback64) { + AChoreographer_postFrameCallback.Reset(); + } + +#if FML_ARCH_CPU_32_BITS + // On 32-bit platforms, the nanosecond resolution timestamp causes overflow on + // the argument in the callback. Don't use it on those platforms. + AChoreographer_postFrameCallback.Reset(); +#endif // FML_ARCH_CPU_32_BITS + + is_valid_ = true; +} + +ProcTable::~ProcTable() = default; + +bool ProcTable::IsValid() const { + return is_valid_; +} + +bool ProcTable::TraceIsEnabled() const { + return this->ATrace_isEnabled ? this->ATrace_isEnabled() : false; +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/proc_table.h b/impeller/toolkit/android/proc_table.h new file mode 100644 index 0000000000000..83b38a1293c79 --- /dev/null +++ b/impeller/toolkit/android/proc_table.h @@ -0,0 +1,129 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_PROC_TABLE_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_PROC_TABLE_H_ + +#include +#define EGL_EGLEXT_PROTOTYPES +#include +#include +#include +#include +#include +#include + +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/native_library.h" + +namespace impeller::android { + +//------------------------------------------------------------------------------ +/// @brief The Android procs along with the device API level on which these +/// will be available. There is no checking of the actual API level +/// however (because getting the API level is itself only possible +/// on API levels 29 and above). +/// +/// Take care to explicitly check for the availability of these APIs +/// at runtime before invoking them. +/// +/// Typically, you'll never have to deal with the proc. table +/// directly. Instead, rely on the handle wrappers (`Choreographer`, +/// `HardwareBuffer`, etc..). +/// +#define FOR_EACH_ANDROID_PROC(INVOKE) \ + INVOKE(AChoreographer_getInstance, 24) \ + INVOKE(AChoreographer_postFrameCallback, 24) \ + INVOKE(AChoreographer_postFrameCallback64, 29) \ + INVOKE(AHardwareBuffer_acquire, 26) \ + INVOKE(AHardwareBuffer_allocate, 26) \ + INVOKE(AHardwareBuffer_describe, 26) \ + INVOKE(AHardwareBuffer_fromHardwareBuffer, 26) \ + INVOKE(AHardwareBuffer_getId, 31) \ + INVOKE(AHardwareBuffer_isSupported, 29) \ + INVOKE(AHardwareBuffer_release, 26) \ + INVOKE(ANativeWindow_acquire, 0) \ + INVOKE(ANativeWindow_getHeight, 0) \ + INVOKE(ANativeWindow_getWidth, 0) \ + INVOKE(ANativeWindow_release, 0) \ + INVOKE(ASurfaceControl_createFromWindow, 29) \ + INVOKE(ASurfaceControl_release, 29) \ + INVOKE(ASurfaceTransaction_apply, 29) \ + INVOKE(ASurfaceTransaction_create, 29) \ + INVOKE(ASurfaceTransaction_delete, 29) \ + INVOKE(ASurfaceTransaction_reparent, 29) \ + INVOKE(ASurfaceTransaction_setBuffer, 29) \ + INVOKE(ASurfaceTransaction_setColor, 29) \ + INVOKE(ASurfaceTransaction_setOnComplete, 29) \ + INVOKE(ATrace_isEnabled, 23) \ + INVOKE(eglGetNativeClientBufferANDROID, 0) + +template +struct AndroidProc { + using AndroidProcType = T; + + const char* proc_name = nullptr; + + AndroidProcType* proc = nullptr; + + constexpr bool IsAvailable() const { return proc != nullptr; } + + explicit constexpr operator bool() const { return IsAvailable(); } + + template + auto operator()(Args&&... args) const { + FML_DCHECK(IsAvailable()) + << "Android method " << proc_name + << " is not available on this device. Missing check."; + return proc(std::forward(args)...); + } + + void Reset() { proc = nullptr; } +}; + +//------------------------------------------------------------------------------ +/// @brief The table of Android procs that are resolved dynamically. +/// +struct ProcTable { + ProcTable(); + + ~ProcTable(); + + ProcTable(const ProcTable&) = delete; + + ProcTable& operator=(const ProcTable&) = delete; + + //---------------------------------------------------------------------------- + /// @brief If a valid proc table could be setup. This may fail in case of + /// setup on non-Android platforms. + /// + /// @return `true` if valid. + /// + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Check if tracing in enabled in the process. This call can be + /// made at any API level. + /// + /// @return If tracing is enabled. + /// + bool TraceIsEnabled() const; + +#define DEFINE_PROC(name, api) \ + AndroidProc name = {.proc_name = #name}; + FOR_EACH_ANDROID_PROC(DEFINE_PROC); +#undef DEFINE_PROC + + private: + std::vector> libraries_; + bool is_valid_ = false; +}; + +const ProcTable& GetProcTable(); + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_PROC_TABLE_H_ diff --git a/impeller/toolkit/android/surface_control.cc b/impeller/toolkit/android/surface_control.cc new file mode 100644 index 0000000000000..71282e7ccc981 --- /dev/null +++ b/impeller/toolkit/android/surface_control.cc @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/toolkit/android/surface_control.h" + +#include "impeller/base/validation.h" +#include "impeller/toolkit/android/surface_transaction.h" + +namespace impeller::android { + +SurfaceControl::SurfaceControl(ANativeWindow* window, const char* debug_name) { + if (window == nullptr) { + VALIDATION_LOG << "Parent window of surface was null."; + return; + } + if (debug_name == nullptr) { + debug_name = "Impeller Layer"; + } + control_.reset( + GetProcTable().ASurfaceControl_createFromWindow(window, debug_name)); +} + +SurfaceControl::~SurfaceControl() { + if (IsValid() && !RemoveFromParent()) { + VALIDATION_LOG << "Surface control could not be removed from its parent. " + "Expect a leak."; + } +} + +bool SurfaceControl::IsValid() const { + return control_.is_valid(); +} + +ASurfaceControl* SurfaceControl::GetHandle() const { + return control_.get(); +} + +bool SurfaceControl::RemoveFromParent() const { + if (!IsValid()) { + return false; + } + SurfaceTransaction transaction; + if (!transaction.SetParent(*this, nullptr)) { + return false; + } + return transaction.Apply(); +} + +bool SurfaceControl::IsAvailableOnPlatform() { + return GetProcTable().IsValid() && + GetProcTable().ASurfaceControl_createFromWindow.IsAvailable(); +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/surface_control.h b/impeller/toolkit/android/surface_control.h new file mode 100644 index 0000000000000..af51a4bc5b748 --- /dev/null +++ b/impeller/toolkit/android/surface_control.h @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_CONTROL_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_CONTROL_H_ + +#include "flutter/fml/unique_object.h" +#include "impeller/toolkit/android/proc_table.h" + +namespace impeller::android { + +//------------------------------------------------------------------------------ +/// @brief A wrapper for ASurfaceControl. +/// https://developer.android.com/ndk/reference/group/native-activity#asurfacecontrol +/// +/// Instances of this class represent a node in the hierarchy of +/// surfaces sent to the system compositor for final composition. +/// +/// This wrapper is only available on Android API 29 and above. +/// +class SurfaceControl { + public: + //---------------------------------------------------------------------------- + /// @return `true` if any surface controls can be created on this + /// platform. + /// + static bool IsAvailableOnPlatform(); + + //---------------------------------------------------------------------------- + /// @brief Creates a new surface control and adds it as a child of the + /// given window. + /// + /// @param window The window + /// @param[in] debug_name A debug name. See it using + /// `adb shell dumpsys SurfaceFlinger` along with + /// other control properties. If no debug name is + /// specified, the value "Impeller Layer" is used. + /// + explicit SurfaceControl(ANativeWindow* window, + const char* debug_name = nullptr); + + //---------------------------------------------------------------------------- + /// @brief Removes the surface control from the presentation hierarchy + /// managed by the system compositor and release the client side + /// reference to the control. At this point, it may be collected + /// when the compositor is also done using it. + /// + ~SurfaceControl(); + + SurfaceControl(const SurfaceControl&) = delete; + + SurfaceControl& operator=(const SurfaceControl&) = delete; + + bool IsValid() const; + + ASurfaceControl* GetHandle() const; + + //---------------------------------------------------------------------------- + /// @brief Remove the surface control from the hierarchy of nodes + /// presented by the system compositor. + /// + /// @return `true` If the control will be removed from the hierarchy of + /// nodes presented by the system compositor. + /// + bool RemoveFromParent() const; + + private: + struct UniqueASurfaceControlTraits { + static ASurfaceControl* InvalidValue() { return nullptr; } + + static bool IsValid(ASurfaceControl* value) { + return value != InvalidValue(); + } + + static void Free(ASurfaceControl* value) { + GetProcTable().ASurfaceControl_release(value); + } + }; + + fml::UniqueObject control_; +}; + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_CONTROL_H_ diff --git a/impeller/toolkit/android/surface_transaction.cc b/impeller/toolkit/android/surface_transaction.cc new file mode 100644 index 0000000000000..a96eef6a26a57 --- /dev/null +++ b/impeller/toolkit/android/surface_transaction.cc @@ -0,0 +1,104 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/impeller/toolkit/android/surface_transaction.h" + +#include "flutter/impeller/toolkit/android/hardware_buffer.h" +#include "flutter/impeller/toolkit/android/surface_control.h" +#include "impeller/base/validation.h" + +namespace impeller::android { + +SurfaceTransaction::SurfaceTransaction() + : transaction_(GetProcTable().ASurfaceTransaction_create()) {} + +SurfaceTransaction::~SurfaceTransaction() = default; + +bool SurfaceTransaction::IsValid() const { + return transaction_.is_valid(); +} + +struct TransactionInFlightData { + SurfaceTransaction::OnCompleteCallback callback; +}; + +bool SurfaceTransaction::Apply(OnCompleteCallback callback) { + if (!IsValid()) { + return false; + } + + if (!callback) { + callback = []() {}; + } + + const auto& proc_table = GetProcTable(); + + auto data = std::make_unique(); + data->callback = callback; + proc_table.ASurfaceTransaction_setOnComplete( + transaction_.get(), // + data.release(), // + [](void* context, ASurfaceTransactionStats* stats) -> void { + auto data = reinterpret_cast(context); + data->callback(); + delete data; + }); + proc_table.ASurfaceTransaction_apply(transaction_.get()); + + // Transactions may not be applied over and over. + transaction_.reset(); + return true; +} + +bool SurfaceTransaction::SetContents(const SurfaceControl* control, + const HardwareBuffer* buffer) { + if (control == nullptr || buffer == nullptr) { + VALIDATION_LOG << "Invalid control or buffer."; + return false; + } + GetProcTable().ASurfaceTransaction_setBuffer(transaction_.get(), // + control->GetHandle(), // + buffer->GetHandle(), // + -1); + return true; +} + +bool SurfaceTransaction::SetBackgroundColor(const SurfaceControl& control, + const Color& color) { + if (!IsValid() || !control.IsValid()) { + return false; + } + GetProcTable().ASurfaceTransaction_setColor(transaction_.get(), // + control.GetHandle(), // + color.red, // + color.green, // + color.blue, // + color.alpha, // + ADATASPACE_SRGB_LINEAR // + ); + return true; +} + +bool SurfaceTransaction::SetParent(const SurfaceControl& control, + const SurfaceControl* new_parent) { + if (!IsValid() || !control.IsValid()) { + return false; + } + if (new_parent && !new_parent->IsValid()) { + return false; + } + GetProcTable().ASurfaceTransaction_reparent( + transaction_.get(), // + control.GetHandle(), // + new_parent == nullptr ? nullptr : new_parent->GetHandle() // + ); + return true; +} + +bool SurfaceTransaction::IsAvailableOnPlatform() { + return GetProcTable().IsValid() && + GetProcTable().ASurfaceTransaction_create.IsAvailable(); +} + +} // namespace impeller::android diff --git a/impeller/toolkit/android/surface_transaction.h b/impeller/toolkit/android/surface_transaction.h new file mode 100644 index 0000000000000..b9460f35e0978 --- /dev/null +++ b/impeller/toolkit/android/surface_transaction.h @@ -0,0 +1,135 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_H_ +#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_H_ + +#include +#include + +#include "flutter/fml/unique_object.h" +#include "impeller/geometry/color.h" +#include "impeller/toolkit/android/proc_table.h" + +namespace impeller::android { + +class SurfaceControl; +class HardwareBuffer; + +//------------------------------------------------------------------------------ +/// @brief A wrapper for ASurfaceTransaction. +/// https://developer.android.com/ndk/reference/group/native-activity#asurfacetransaction +/// +/// A surface transaction is a collection of updates to the +/// hierarchy of surfaces (represented by `ASurfaceControl` +/// instances) that are applied atomically in the compositor. +/// +/// This wrapper is only available on Android API 29 and above. +/// +/// @note Transactions should be short lived objects (create, apply, +/// collect). But, if these are used on multiple threads, they must +/// be externally synchronized. +/// +class SurfaceTransaction { + public: + //---------------------------------------------------------------------------- + /// @return `true` if any surface transactions can be created on this + /// platform. + /// + static bool IsAvailableOnPlatform(); + + SurfaceTransaction(); + + ~SurfaceTransaction(); + + SurfaceTransaction(const SurfaceTransaction&) = delete; + + SurfaceTransaction& operator=(const SurfaceTransaction&) = delete; + + bool IsValid() const; + + //---------------------------------------------------------------------------- + /// @brief Encodes that the updated contents of a surface control are + /// specified by the given hardware buffer. The update will not be + /// committed till the call to `Apply` however. + /// + /// @see `SurfaceTransaction::Apply`. + /// + /// @param[in] control The control + /// @param[in] buffer The hardware buffer + /// + /// @return If the update was encoded in the transaction. + /// + [[nodiscard]] bool SetContents(const SurfaceControl* control, + const HardwareBuffer* buffer); + + //---------------------------------------------------------------------------- + /// @brief Encodes the updated background color of the surface control. + /// The update will not be committed till the call to `Apply` + /// however. + /// + /// @see `SurfaceTransaction::Apply`. + /// + /// @param[in] control The control + /// @param[in] color The color + /// + /// @return `true` if the background control will be set when transaction + /// is applied. + /// + [[nodiscard]] bool SetBackgroundColor(const SurfaceControl& control, + const Color& color); + + using OnCompleteCallback = std::function; + + //---------------------------------------------------------------------------- + /// @brief Applies the updated encoded in the transaction and invokes the + /// callback when the updated are complete. + /// + /// @warning The callback will be invoked on a system managed thread. + /// + /// @note It is fine to immediately destroy the transaction after the + /// call to apply. It is not necessary to wait for transaction + /// completion to collect the transaction handle. + /// + /// @param[in] callback The callback + /// + /// @return `true` if the surface transaction was applied. `true` does not + /// indicate the application was completed however. Only the + /// invocation of the callback denotes transaction completion. + /// + [[nodiscard]] bool Apply(OnCompleteCallback callback = nullptr); + + //---------------------------------------------------------------------------- + /// @brief Set the new parent control of the given control. If the new + /// parent is null, it is removed from the control hierarchy. + /// + /// @param[in] control The control + /// @param[in] new_parent The new parent + /// + /// @return `true` if the control will be re-parented when the transaction + /// is applied. + /// + [[nodiscard]] bool SetParent(const SurfaceControl& control, + const SurfaceControl* new_parent = nullptr); + + private: + struct UniqueASurfaceTransactionTraits { + static ASurfaceTransaction* InvalidValue() { return nullptr; } + + static bool IsValid(ASurfaceTransaction* value) { + return value != InvalidValue(); + } + + static void Free(ASurfaceTransaction* value) { + GetProcTable().ASurfaceTransaction_delete(value); + } + }; + + fml::UniqueObject + transaction_; +}; + +} // namespace impeller::android + +#endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_H_ diff --git a/impeller/toolkit/android/toolkit_android_unittests.cc b/impeller/toolkit/android/toolkit_android_unittests.cc new file mode 100644 index 0000000000000..ff3a7df9daae2 --- /dev/null +++ b/impeller/toolkit/android/toolkit_android_unittests.cc @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/testing/testing.h" +#include "impeller/toolkit/android/choreographer.h" +#include "impeller/toolkit/android/hardware_buffer.h" +#include "impeller/toolkit/android/proc_table.h" +#include "impeller/toolkit/android/surface_control.h" +#include "impeller/toolkit/android/surface_transaction.h" + +namespace impeller::android::testing { + +class ToolkitAndroidTest : public ::testing::Test { + public: + void SetUp() override { + // The toolkit is only available on Android API levels over 29. Skip these + // tests everywhere else. + if (__builtin_available(android 29, *)) { + } else { + GTEST_SKIP() << "Platform too old for this test."; + } + } +}; + +TEST_F(ToolkitAndroidTest, CanCreateProcTable) { + ProcTable proc_table; + ASSERT_TRUE(proc_table.IsValid()); +} + +TEST_F(ToolkitAndroidTest, GuardsAgainstZeroSizedDescriptors) { + auto desc = HardwareBufferDescriptor::MakeForSwapchainImage({0, 0}); + ASSERT_GT(desc.size.width, 0u); + ASSERT_GT(desc.size.height, 0u); +} + +TEST_F(ToolkitAndroidTest, CanCreateHardwareBuffer) { + ASSERT_TRUE(HardwareBuffer::IsAvailableOnPlatform()); + auto desc = HardwareBufferDescriptor::MakeForSwapchainImage({100, 100}); + ASSERT_TRUE(desc.IsAllocatable()); + HardwareBuffer buffer(desc); + ASSERT_TRUE(buffer.IsValid()); +} + +TEST_F(ToolkitAndroidTest, CanGetHardwareBufferIDs) { + ASSERT_TRUE(HardwareBuffer::IsAvailableOnPlatform()); + if (!GetProcTable().AHardwareBuffer_getId.IsAvailable()) { + GTEST_SKIP() << "Hardware buffer IDs are not available on this platform."; + } + auto desc = HardwareBufferDescriptor::MakeForSwapchainImage({100, 100}); + ASSERT_TRUE(desc.IsAllocatable()); + HardwareBuffer buffer(desc); + ASSERT_TRUE(buffer.IsValid()); + ASSERT_TRUE(buffer.GetSystemUniqueID().has_value()); +} + +TEST_F(ToolkitAndroidTest, CanDescribeHardwareBufferHandles) { + ASSERT_TRUE(HardwareBuffer::IsAvailableOnPlatform()); + auto desc = HardwareBufferDescriptor::MakeForSwapchainImage({100, 100}); + ASSERT_TRUE(desc.IsAllocatable()); + HardwareBuffer buffer(desc); + ASSERT_TRUE(buffer.IsValid()); + auto a_desc = HardwareBuffer::Describe(buffer.GetHandle()); + ASSERT_TRUE(a_desc.has_value()); + ASSERT_EQ(a_desc->width, 100u); // NOLINT + ASSERT_EQ(a_desc->height, 100u); // NOLINT +} + +TEST_F(ToolkitAndroidTest, CanApplySurfaceTransaction) { + ASSERT_TRUE(SurfaceTransaction::IsAvailableOnPlatform()); + SurfaceTransaction transaction; + ASSERT_TRUE(transaction.IsValid()); + fml::AutoResetWaitableEvent event; + ASSERT_TRUE(transaction.Apply([&event]() { event.Signal(); })); + event.Wait(); +} + +TEST_F(ToolkitAndroidTest, SurfacControlsAreAvailable) { + ASSERT_TRUE(SurfaceControl::IsAvailableOnPlatform()); +} + +TEST_F(ToolkitAndroidTest, ChoreographerIsAvailable) { + ASSERT_TRUE(Choreographer::IsAvailableOnPlatform()); +} + +TEST_F(ToolkitAndroidTest, CanPostAndNotWaitForFrameCallbacks) { + const auto& choreographer = Choreographer::GetInstance(); + ASSERT_TRUE(choreographer.IsValid()); + ASSERT_TRUE(choreographer.PostFrameCallback([](auto) {})); +} + +TEST_F(ToolkitAndroidTest, CanPostAndWaitForFrameCallbacks) { + if ((true)) { + GTEST_SKIP() + << "Disabled till the test harness is in an Android activity. " + "Running it without one will hang because the choreographer " + "frame callback will never execute."; + } + const auto& choreographer = Choreographer::GetInstance(); + ASSERT_TRUE(choreographer.IsValid()); + fml::AutoResetWaitableEvent event; + ASSERT_TRUE(choreographer.PostFrameCallback( + [&event](auto point) { event.Signal(); })); + event.Wait(); +} + +} // namespace impeller::android::testing diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 1500fb90e0639..3025fabbbbbec 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -42,7 +42,6 @@ executable("flutter_shell_native_unittests") { visibility = [ "*" ] testonly = true sources = [ - "//flutter/fml/platform/android/ndk_helpers_unittests.cc", "android_context_gl_impeller_unittests.cc", "android_context_gl_unittests.cc", "android_shell_holder_unittests.cc", @@ -143,6 +142,7 @@ source_set("flutter_shell_native_src") { "//flutter/flow", "//flutter/fml", "//flutter/impeller", + "//flutter/impeller/toolkit/android", "//flutter/impeller/toolkit/egl", "//flutter/impeller/toolkit/gles", "//flutter/lib/ui", diff --git a/shell/platform/android/flutter_main.cc b/shell/platform/android/flutter_main.cc index 298fd3aff870e..9c9c58e66abe6 100644 --- a/shell/platform/android/flutter_main.cc +++ b/shell/platform/android/flutter_main.cc @@ -17,7 +17,6 @@ #include "flutter/fml/native_library.h" #include "flutter/fml/paths.h" #include "flutter/fml/platform/android/jni_util.h" -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/fml/platform/android/paths_android.h" #include "flutter/fml/size.h" #include "flutter/lib/ui/plugins/callback_cache.h" @@ -27,6 +26,7 @@ #include "flutter/shell/platform/android/android_context_vulkan_impeller.h" #include "flutter/shell/platform/android/flutter_main.h" #include "impeller/base/validation.h" +#include "impeller/toolkit/android/proc_table.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "txt/platform.h" @@ -85,7 +85,8 @@ void FlutterMain::Init(JNIEnv* env, // Turn systracing on if ATrace_isEnabled is true and the user did not already // request systracing if (!settings.trace_systrace) { - settings.trace_systrace = NDKHelpers::ATrace_isEnabled(); + settings.trace_systrace = + impeller::android::GetProcTable().TraceIsEnabled(); if (settings.trace_systrace) { __android_log_print( ANDROID_LOG_INFO, "Flutter", diff --git a/shell/platform/android/image_external_texture.cc b/shell/platform/android/image_external_texture.cc index 90d78291c988f..b2df3c91de3de 100644 --- a/shell/platform/android/image_external_texture.cc +++ b/shell/platform/android/image_external_texture.cc @@ -5,7 +5,7 @@ #include #include "flutter/fml/platform/android/jni_util.h" -#include "flutter/fml/platform/android/ndk_helpers.h" +#include "flutter/impeller/toolkit/android/proc_table.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" namespace flutter { @@ -107,8 +107,9 @@ AHardwareBuffer* ImageExternalTexture::AHardwareBufferFor( const fml::jni::JavaRef& hardware_buffer) { JNIEnv* env = fml::jni::AttachCurrentThread(); FML_CHECK(env != nullptr); - return NDKHelpers::AHardwareBuffer_fromHardwareBuffer(env, - hardware_buffer.obj()); + const auto& proc = + impeller::android::GetProcTable().AHardwareBuffer_fromHardwareBuffer; + return proc ? proc(env, hardware_buffer.obj()) : nullptr; } } // namespace flutter diff --git a/shell/platform/android/image_external_texture_gl.cc b/shell/platform/android/image_external_texture_gl.cc index 8ac28e46a7470..92743654d0289 100644 --- a/shell/platform/android/image_external_texture_gl.cc +++ b/shell/platform/android/image_external_texture_gl.cc @@ -8,9 +8,9 @@ #include #include "flutter/common/graphics/texture.h" -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/impeller/core/formats.h" #include "flutter/impeller/display_list/dl_image_impeller.h" +#include "flutter/impeller/toolkit/android/hardware_buffer.h" #include "flutter/impeller/toolkit/egl/image.h" #include "flutter/impeller/toolkit/gles/texture.h" #include "third_party/skia/include/core/SkAlphaType.h" @@ -45,7 +45,8 @@ void ImageExternalTextureGL::UpdateImage(JavaLocalRef& hardware_buffer, PaintContext& context) { AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer); std::optional key = - flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer); + impeller::android::HardwareBuffer::GetSystemUniqueID( + latest_hardware_buffer); auto existing_image = image_lru_.FindImage(key); if (existing_image != nullptr) { dl_image_ = existing_image; @@ -89,7 +90,8 @@ impeller::UniqueEGLImageKHR ImageExternalTextureGL::CreateEGLImage( FML_CHECK(display != EGL_NO_DISPLAY); EGLClientBuffer client_buffer = - NDKHelpers::eglGetNativeClientBufferANDROID(hardware_buffer); + impeller::android::GetProcTable().eglGetNativeClientBufferANDROID( + hardware_buffer); FML_DCHECK(client_buffer != nullptr); if (client_buffer == nullptr) { FML_LOG(ERROR) << "eglGetNativeClientBufferAndroid returned null."; diff --git a/shell/platform/android/image_external_texture_gl.h b/shell/platform/android/image_external_texture_gl.h index 6a132237dbe3d..995592c7787f5 100644 --- a/shell/platform/android/image_external_texture_gl.h +++ b/shell/platform/android/image_external_texture_gl.h @@ -16,8 +16,6 @@ #include "flutter/impeller/toolkit/egl/egl.h" #include "flutter/impeller/toolkit/egl/image.h" #include "flutter/impeller/toolkit/gles/texture.h" - -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/shell/platform/android/android_context_gl_skia.h" namespace flutter { diff --git a/shell/platform/android/image_external_texture_vk.cc b/shell/platform/android/image_external_texture_vk.cc index ad00687b555ba..88574548d6c11 100644 --- a/shell/platform/android/image_external_texture_vk.cc +++ b/shell/platform/android/image_external_texture_vk.cc @@ -1,8 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. #include "flutter/shell/platform/android/image_external_texture_vk.h" + #include -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/impeller/core/formats.h" #include "flutter/impeller/core/texture_descriptor.h" #include "flutter/impeller/display_list/dl_image_impeller.h" @@ -10,6 +13,7 @@ #include "flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h" #include "flutter/impeller/renderer/backend/vulkan/command_encoder_vk.h" #include "flutter/impeller/renderer/backend/vulkan/texture_vk.h" +#include "flutter/impeller/toolkit/android/hardware_buffer.h" namespace flutter { @@ -41,13 +45,13 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context, JavaLocalRef hardware_buffer = HardwareBufferFor(image); AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer); - AHardwareBuffer_Desc hb_desc = {}; - flutter::NDKHelpers::AHardwareBuffer_describe(latest_hardware_buffer, - &hb_desc); + auto hb_desc = + impeller::android::HardwareBuffer::Describe(latest_hardware_buffer); std::optional key = - flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer); + impeller::android::HardwareBuffer::GetSystemUniqueID( + latest_hardware_buffer); auto existing_image = image_lru_.FindImage(key); - if (existing_image != nullptr) { + if (existing_image != nullptr || !hb_desc.has_value()) { dl_image_ = existing_image; CloseHardwareBuffer(hardware_buffer); @@ -55,7 +59,7 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context, } auto texture_source = std::make_shared( - impeller_context_, latest_hardware_buffer, hb_desc); + impeller_context_, latest_hardware_buffer, hb_desc.value()); if (!texture_source->IsValid()) { CloseHardwareBuffer(hardware_buffer); return; diff --git a/shell/platform/android/image_lru.h b/shell/platform/android/image_lru.h index 28a31cd2bf842..63829efb970ac 100644 --- a/shell/platform/android/image_lru.h +++ b/shell/platform/android/image_lru.h @@ -9,7 +9,6 @@ #include #include "display_list/image/dl_image.h" -#include "fml/platform/android/ndk_helpers.h" namespace flutter { @@ -20,6 +19,8 @@ namespace flutter { // necessary. static constexpr size_t kImageReaderSwapchainSize = 6u; +using HardwareBufferKey = uint64_t; + class ImageLRU { public: ImageLRU() = default; diff --git a/shell/platform/android/library_loader.cc b/shell/platform/android/library_loader.cc index 9f3691d818512..644bd5de3b8ec 100644 --- a/shell/platform/android/library_loader.cc +++ b/shell/platform/android/library_loader.cc @@ -3,7 +3,6 @@ // found in the LICENSE file. #include "flutter/fml/platform/android/jni_util.h" -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/shell/platform/android/android_image_generator.h" #include "flutter/shell/platform/android/flutter_main.h" #include "flutter/shell/platform/android/platform_view_android.h" @@ -14,9 +13,6 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { // Initialize the Java VM. fml::jni::InitJavaVM(vm); - // Registery dlsym lookups for NDK functions - flutter::NDKHelpers::Init(); - JNIEnv* env = fml::jni::AttachCurrentThread(); bool result = false; diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index b6d5dfc2f504f..0b4ed2ca1fead 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -12,7 +12,6 @@ #include #include -#include "flutter/fml/platform/android/ndk_helpers.h" #include "include/android/SkImageAndroid.h" #include "unicode/uchar.h" diff --git a/shell/platform/android/vsync_waiter_android.cc b/shell/platform/android/vsync_waiter_android.cc index d0a8db383a0ce..bfced030f5fe5 100644 --- a/shell/platform/android/vsync_waiter_android.cc +++ b/shell/platform/android/vsync_waiter_android.cc @@ -10,10 +10,10 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/logging.h" #include "flutter/fml/platform/android/jni_util.h" -#include "flutter/fml/platform/android/ndk_helpers.h" #include "flutter/fml/platform/android/scoped_java_ref.h" #include "flutter/fml/size.h" #include "flutter/fml/trace_event.h" +#include "impeller/toolkit/android/choreographer.h" namespace flutter { @@ -28,47 +28,35 @@ VsyncWaiterAndroid::~VsyncWaiterAndroid() = default; // |VsyncWaiter| void VsyncWaiterAndroid::AwaitVSync() { - switch (NDKHelpers::ChoreographerSupported()) { - case ChoreographerSupportStatus::kSupported32: { - auto* weak_this = new std::weak_ptr(shared_from_this()); - fml::TaskRunner::RunNowOrPostTask( - task_runners_.GetUITaskRunner(), [weak_this]() { - NDKHelpers::AChoreographer_postFrameCallback( - NDKHelpers::AChoreographer_getInstance(), &OnVsyncFromNDK32, - weak_this); + if (impeller::android::Choreographer::IsAvailableOnPlatform()) { + auto* weak_this = new std::weak_ptr(shared_from_this()); + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), [weak_this]() { + const auto& choreographer = + impeller::android::Choreographer::GetInstance(); + choreographer.PostFrameCallback([weak_this](auto time) { + auto time_ns = + std::chrono::time_point_cast(time) + .time_since_epoch() + .count(); + OnVsyncFromNDK(time_ns, weak_this); }); - } break; - case ChoreographerSupportStatus::kSupported64: { - auto* weak_this = new std::weak_ptr(shared_from_this()); - fml::TaskRunner::RunNowOrPostTask( - task_runners_.GetUITaskRunner(), [weak_this]() { - NDKHelpers::AChoreographer_postFrameCallback64( - NDKHelpers::AChoreographer_getInstance(), &OnVsyncFromNDK, - weak_this); - }); - } break; - case ChoreographerSupportStatus::kUnsupported: { - // TODO(99798): Remove it when we drop support for API level < 29. - auto* weak_this = new std::weak_ptr(shared_from_this()); - jlong java_baton = reinterpret_cast(weak_this); - task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() { - JNIEnv* env = fml::jni::AttachCurrentThread(); - env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), // - g_async_wait_for_vsync_method_, // - java_baton // - ); - }); - } break; + }); + } else { + // TODO(99798): Remove it when we drop support for API level < 29 and 32-bit + // devices. + auto* weak_this = new std::weak_ptr(shared_from_this()); + jlong java_baton = reinterpret_cast(weak_this); + task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() { + JNIEnv* env = fml::jni::AttachCurrentThread(); + env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), // + g_async_wait_for_vsync_method_, // + java_baton // + ); + }); } } -// static -void VsyncWaiterAndroid::OnVsyncFromNDK32( - long frame_nanos, // NOLINT to match a deprecated NDK interface. - void* data) { - OnVsyncFromNDK(frame_nanos, data); -} - // static void VsyncWaiterAndroid::OnVsyncFromNDK(int64_t frame_nanos, void* data) { auto frame_time = fml::TimePoint::FromEpochDelta( diff --git a/shell/platform/android/vsync_waiter_android.h b/shell/platform/android/vsync_waiter_android.h index 69c3ad912839c..7129bb94305e2 100644 --- a/shell/platform/android/vsync_waiter_android.h +++ b/shell/platform/android/vsync_waiter_android.h @@ -29,9 +29,6 @@ class VsyncWaiterAndroid final : public VsyncWaiter { void AwaitVSync() override; static void OnVsyncFromNDK(int64_t frame_nanos, void* data); - // This needs to match a deprecated NDK interface. - static void OnVsyncFromNDK32(long frame_nanos, // NOLINT - void* data); static void OnVsyncFromJava(JNIEnv* env, jclass jcaller, diff --git a/testing/run_tests.py b/testing/run_tests.py index d91ec5de3a629..d51ad01f2b1c5 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -729,16 +729,21 @@ def run_java_tests(executable_filter, android_variant='android_debug_unopt'): run_cmd(command, cwd=test_runner_dir, env=env) -def run_android_tests(android_variant='android_debug_unopt', adb_path=None): - test_runner_name = 'flutter_shell_native_unittests' +def run_android_unittest(test_runner_name, android_variant, adb_path): tests_path = os.path.join(OUT_DIR, android_variant, test_runner_name) remote_path = '/data/local/tmp' remote_tests_path = os.path.join(remote_path, test_runner_name) - if adb_path is None: - adb_path = 'adb' run_cmd([adb_path, 'push', tests_path, remote_path], cwd=BUILDROOT_DIR) run_cmd([adb_path, 'shell', remote_tests_path]) + +def run_android_tests(android_variant='android_debug_unopt', adb_path=None): + if adb_path is None: + adb_path = 'adb' + + run_android_unittest('flutter_shell_native_unittests', android_variant, adb_path) + run_android_unittest('impeller_toolkit_android_unittests', android_variant, adb_path) + systrace_test = os.path.join(BUILDROOT_DIR, 'flutter', 'testing', 'android_systrace_test.py') scenario_apk = os.path.join(OUT_DIR, android_variant, 'firebase_apks', 'scenario_app.apk') run_cmd([