diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index ac484f21f4601..081bc2a8acc5c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -122,6 +122,7 @@ public void loadLibrary() { System.loadLibrary("flutter"); FlutterJNI.loadLibraryCalled = true; + nativeSetRefreshRateFPS(refreshRateFPS); } private static boolean loadLibraryCalled = false; @@ -227,6 +228,8 @@ public static void setRefreshRateFPS(float refreshRateFPS) { private static boolean setRefreshRateFPSCalled = false; + private static native void nativeSetRefreshRateFPS(float refreshRateFPS); + // TODO(mattcarroll): add javadocs public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { asyncWaitForVsyncDelegate = delegate; diff --git a/shell/platform/android/vsync_waiter_android.cc b/shell/platform/android/vsync_waiter_android.cc index 1a69705f634d4..7acee9749f971 100644 --- a/shell/platform/android/vsync_waiter_android.cc +++ b/shell/platform/android/vsync_waiter_android.cc @@ -14,13 +14,40 @@ #include "flutter/fml/size.h" #include "flutter/fml/trace_event.h" +#include +#include +#include +#include + namespace flutter { static fml::jni::ScopedJavaGlobalRef* g_vsync_waiter_class = nullptr; static jmethodID g_async_wait_for_vsync_method_ = nullptr; +static double refreshRateFPS_ = 60.0; VsyncWaiterAndroid::VsyncWaiterAndroid(flutter::TaskRunners task_runners) - : VsyncWaiter(std::move(task_runners)) {} + : VsyncWaiter(std::move(task_runners)) { + void* libAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (libAndroid == nullptr) { + FML_LOG(ERROR) << "FATAL: cannot open libandroid.so: " << errno; + return; + } + + mAChoreographer_getInstance = + reinterpret_cast( + dlsym(libAndroid, "AChoreographer_getInstance")); + + mAChoreographer_postFrameCallback = + reinterpret_cast( + dlsym(libAndroid, "AChoreographer_postFrameCallback")); + + if (!mAChoreographer_getInstance || !mAChoreographer_postFrameCallback) { + FML_LOG(ERROR) << "FATAL: cannot get AChoreographer symbols"; + return; + } else { + useAChoreographer_ = true; + } +} VsyncWaiterAndroid::~VsyncWaiterAndroid() = default; @@ -28,14 +55,44 @@ VsyncWaiterAndroid::~VsyncWaiterAndroid() = default; void VsyncWaiterAndroid::AwaitVSync() { auto* weak_this = new std::weak_ptr(shared_from_this()); jlong java_baton = reinterpret_cast(weak_this); + if (!useAChoreographer_) { + 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 // + ); + }); + } else { + mAChoreographer_postFrameCallback(mAChoreographer_getInstance(), + VsyncWaiterAndroid::OnAChoreographerVsync, + reinterpret_cast(java_baton)); + } +} + +void VsyncWaiterAndroid::OnAChoreographerVsync(long frameTimeNanos, + void* java_baton) { + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + auto now = tp.tv_sec * (1000 * 1000 * 1000) + tp.tv_nsec; + long delay = now - frameTimeNanos; + if (delay < 0) { + delay = 0; + } + + auto frame_time = + fml::TimePoint::Now() - fml::TimeDelta::FromNanoseconds(delay); + auto target_time = frame_time + fml::TimeDelta::FromNanoseconds( + 1000000000.0 / refreshRateFPS_); + + ConsumePendingCallback(reinterpret_cast(java_baton), frame_time, + target_time); +} - 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 // - ); - }); +void VsyncWaiterAndroid::SetRefreshRateFPS(JNIEnv* env, + jobject jcaller, + jfloat refreshRateFPS) { + refreshRateFPS_ = static_cast(refreshRateFPS); } // static @@ -70,11 +127,17 @@ void VsyncWaiterAndroid::ConsumePendingCallback( // static bool VsyncWaiterAndroid::Register(JNIEnv* env) { - static const JNINativeMethod methods[] = {{ - .name = "nativeOnVsync", - .signature = "(JJJ)V", - .fnPtr = reinterpret_cast(&OnNativeVsync), - }}; + static const JNINativeMethod methods[] = { + { + .name = "nativeOnVsync", + .signature = "(JJJ)V", + .fnPtr = reinterpret_cast(&OnNativeVsync), + }, + { + .name = "nativeSetRefreshRateFPS", + .signature = "(F)V", + .fnPtr = reinterpret_cast(&SetRefreshRateFPS), + }}; jclass clazz = env->FindClass("io/flutter/embedding/engine/FlutterJNI"); diff --git a/shell/platform/android/vsync_waiter_android.h b/shell/platform/android/vsync_waiter_android.h index 03bc415e3ae99..29639759a4a5f 100644 --- a/shell/platform/android/vsync_waiter_android.h +++ b/shell/platform/android/vsync_waiter_android.h @@ -14,6 +14,29 @@ namespace flutter { +struct AChoreographer; +typedef struct AChoreographer AChoreographer; + +/** + * Prototype of the function that is called when a new frame is being rendered. + * It's passed the time that the frame is being rendered as nanoseconds in the + * CLOCK_MONOTONIC time base, as well as the data pointer provided by the + * application that registered a callback. All callbacks that run as part of + * rendering a frame will observe the same frame time, so it should be used + * whenever events need to be synchronized (e.g. animations). + */ +typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void* data); + +// AChoreographer is supported from API 24. To allow compilation for minSDK < 24 +// and still use AChoreographer for SDK >= 24 we need runtime support to call +// AChoreographer APIs. +using PFN_AChoreographer_getInstance = AChoreographer* (*)(); + +using PFN_AChoreographer_postFrameCallback = + void (*)(AChoreographer* choreographer, + AChoreographer_frameCallback callback, + void* data); + class VsyncWaiterAndroid final : public VsyncWaiter { public: static bool Register(JNIEnv* env); @@ -32,11 +55,23 @@ class VsyncWaiterAndroid final : public VsyncWaiter { jlong refreshPeriodNanos, jlong java_baton); + static void OnAChoreographerVsync(long frameTimeNanos, void* java_baton); + + static void SetRefreshRateFPS(JNIEnv* env, + jobject jcaller, + jfloat refreshRateFPS); + static void ConsumePendingCallback(jlong java_baton, fml::TimePoint frame_start_time, fml::TimePoint frame_target_time); FML_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterAndroid); + + private: + PFN_AChoreographer_getInstance mAChoreographer_getInstance = nullptr; + PFN_AChoreographer_postFrameCallback mAChoreographer_postFrameCallback = + nullptr; + bool useAChoreographer_ = false; }; } // namespace flutter