Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.h
FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.h
FILE: ../../../flutter/shell/platform/android/AndroidManifest.xml
FILE: ../../../flutter/shell/platform/android/android_choreographer.cc
FILE: ../../../flutter/shell/platform/android/android_choreographer.h
FILE: ../../../flutter/shell/platform/android/android_context_gl.cc
FILE: ../../../flutter/shell/platform/android/android_context_gl.h
FILE: ../../../flutter/shell/platform/android/android_context_gl_unittests.cc
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ source_set("flutter_shell_native_src") {

sources = [
"$root_build_dir/flutter_icu/icudtl.o",
"android_choreographer.cc",
"android_choreographer.h",
"android_context_gl.cc",
"android_context_gl.h",
"android_display.cc",
Expand Down
61 changes: 61 additions & 0 deletions shell/platform/android/android_choreographer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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/android_choreographer.h"

#include "flutter/fml/native_library.h"

// Only avialalbe on API 24+
typedef void AChoreographer;
// Only available on API 29+ or API 24+ if the architecture is 64-bit.
typedef void (*AChoreographer_frameCallback)(int64_t frameTimeNanos,
void* data);
// Only avialalbe on API 24+
typedef AChoreographer* (*AChoreographer_getInstance_FPN)();
typedef void (*AChoreographer_postFrameCallback_FPN)(
AChoreographer* choreographer,
AChoreographer_frameCallback callback,
void* data);
static AChoreographer_getInstance_FPN AChoreographer_getInstance;
static AChoreographer_postFrameCallback_FPN AChoreographer_postFrameCallback;

namespace flutter {

bool AndroidChoreographer::ShouldUseNDKChoreographer() {
static std::optional<bool> use_ndk_choreographer;
if (use_ndk_choreographer) {
return use_ndk_choreographer.value();
}
auto libandroid = fml::NativeLibrary::Create("libandroid.so");
FML_DCHECK(libandroid);
auto get_instance_fn =
libandroid->ResolveFunction<AChoreographer_getInstance_FPN>(
"AChoreographer_getInstance");
auto post_frame_callback_fn =
libandroid->ResolveFunction<AChoreographer_postFrameCallback_FPN>(
"AChoreographer_postFrameCallback64");
#if FML_ARCH_CPU_64_BITS
if (!post_frame_callback_fn) {
post_frame_callback_fn =
libandroid->ResolveFunction<AChoreographer_postFrameCallback_FPN>(
"AChoreographer_postFrameCallback");
}
#endif
if (get_instance_fn && post_frame_callback_fn) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this right?

if !FML_ARCH_CPU_64_BITS and Choreographer_postFrameCallback64 == nullptr, then should_use_ndk_choreographer returns false.

Copy link
Member Author

@ColdPaleLight ColdPaleLight Mar 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can not use AChoreographer_postFrameCallback in 32-bits app, because in the callback function AChoreographer_frameCallback(long frameTimeNanos, void *data), the type of frameTimeNanos is long, and in 32-bits architecture long is 32bit. And actually the correct frameTimeNanos should be 64 bit. So in 32-bits architecture we can't get the correct frameTimeNanos.

I have tested on two devices and the conclusion is that the 32-bits flutter app does not work correctly if using AChoreographer_postFrameCallback

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I didn't realize this was going to be this complicated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I didn't realize this was going to be this complicated.

We can also simplify it, for example , API 29+ use NDK AChoreographer_postFrameCallback64, others use Java fallback. So that we don't need to consider whether the app is 32-bits or 64-bits.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we are doing this, then making it work on API level >=24 for x64 is better than only >= 29.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it.

AChoreographer_getInstance = get_instance_fn.value();
AChoreographer_postFrameCallback = post_frame_callback_fn.value();
use_ndk_choreographer = true;
} else {
use_ndk_choreographer = false;
}
return use_ndk_choreographer.value();
}

void AndroidChoreographer::PostFrameCallback(OnFrameCallback callback,
void* data) {
AChoreographer* choreographer = AChoreographer_getInstance();
AChoreographer_postFrameCallback(choreographer, callback, data);
}

} // namespace flutter
30 changes: 30 additions & 0 deletions shell/platform/android/android_choreographer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_
#define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_

#include "flutter/fml/macros.h"

#include <cstdint>

namespace flutter {

//------------------------------------------------------------------------------
/// The Android Choreographer is used by `VsyncWaiterAndroid` to await vsync
/// signal. It's only available on API 29+ or API 24+ if the architecture is
/// 64-bit.
///
class AndroidChoreographer {
public:
typedef void (*OnFrameCallback)(int64_t frame_time_nanos, void* data);
static bool ShouldUseNDKChoreographer();
static void PostFrameCallback(OnFrameCallback callback, void* data);

FML_DISALLOW_COPY_AND_ASSIGN(AndroidChoreographer);
};

} // namespace flutter

#endif // FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_
10 changes: 10 additions & 0 deletions shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,18 @@ public void setRefreshRateFPS(float refreshRateFPS) {
// on Android we will need to refactor this. Static lookup makes things a
// bit easier on the C++ side.
FlutterJNI.refreshRateFPS = refreshRateFPS;
updateRefreshRate();
}

public void updateRefreshRate() {
if (!FlutterJNI.loadLibraryCalled) {
return;
}
nativeUpdateRefreshRate(refreshRateFPS);
}

private native void nativeUpdateRefreshRate(float refreshRateFPS);

/**
* The Android vsync waiter implementation in C++ needs to know when a vsync signal arrives, which
* is obtained via Java API. The delegate set here is called on the C++ side when the engine is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public InitResult call() {
ResourceExtractor resourceExtractor = initResources(appContext);

flutterJNI.loadLibrary();
flutterJNI.updateRefreshRate();

// Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -245,4 +246,13 @@ public void invokePlatformMessageResponseCallback__wantsDirectBuffer() {
ByteBuffer buffer = ByteBuffer.allocate(4);
flutterJNI.invokePlatformMessageResponseCallback(0, buffer, buffer.position());
}

@Test
public void setRefreshRateFPS__callsUpdateRefreshRate() {
FlutterJNI flutterJNI = spy(new FlutterJNI());
// --- Execute Test ---
flutterJNI.setRefreshRateFPS(120.0f);
// --- Verify Results ---
verify(flutterJNI, times(1)).updateRefreshRate();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public void itReportsInitializedAfterInitializing() {
shadowOf(getMainLooper()).idle();
assertTrue(flutterLoader.initialized());
verify(mockFlutterJNI, times(1)).loadLibrary();
verify(mockFlutterJNI, times(1)).updateRefreshRate();
}

@Test
Expand Down
92 changes: 66 additions & 26 deletions shell/platform/android/vsync_waiter_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,81 @@
#include "flutter/fml/platform/android/scoped_java_ref.h"
#include "flutter/fml/size.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/platform/android/android_choreographer.h"

namespace flutter {

static fml::jni::ScopedJavaGlobalRef<jclass>* g_vsync_waiter_class = nullptr;
static jmethodID g_async_wait_for_vsync_method_ = nullptr;
static std::atomic_uint g_refresh_rate_ = 60;

VsyncWaiterAndroid::VsyncWaiterAndroid(flutter::TaskRunners task_runners)
: VsyncWaiter(std::move(task_runners)) {}
: VsyncWaiter(std::move(task_runners)),
use_ndk_choreographer_(
AndroidChoreographer::ShouldUseNDKChoreographer()) {}

VsyncWaiterAndroid::~VsyncWaiterAndroid() = default;

// |VsyncWaiter|
void VsyncWaiterAndroid::AwaitVSync() {
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
jlong java_baton = reinterpret_cast<jlong>(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 //
);
});
if (use_ndk_choreographer_) {
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetUITaskRunner(), [weak_this]() {
AndroidChoreographer::PostFrameCallback(&OnVsyncFromNDK, weak_this);
});
} else {
// TODO(99798): Remove it when we drop support for API level < 29.
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
jlong java_baton = reinterpret_cast<jlong>(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::OnVsyncFromNDK(int64_t frame_nanos, void* data) {
TRACE_EVENT0("flutter", "VSYNC");

auto frame_time = fml::TimePoint::FromEpochDelta(
fml::TimeDelta::FromNanoseconds(frame_nanos));
auto now = fml::TimePoint::Now();
if (frame_time > now) {
frame_time = now;
}
auto target_time = frame_time + fml::TimeDelta::FromNanoseconds(
1000000000.0 / g_refresh_rate_);
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(data);
ConsumePendingCallback(weak_this, frame_time, target_time);
}

// static
void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env,
jclass jcaller,
jlong frameDelayNanos,
jlong refreshPeriodNanos,
jlong java_baton) {
void VsyncWaiterAndroid::OnVsyncFromJava(JNIEnv* env,
jclass jcaller,
jlong frameDelayNanos,
jlong refreshPeriodNanos,
jlong java_baton) {
TRACE_EVENT0("flutter", "VSYNC");

auto frame_time =
fml::TimePoint::Now() - fml::TimeDelta::FromNanoseconds(frameDelayNanos);
auto target_time =
frame_time + fml::TimeDelta::FromNanoseconds(refreshPeriodNanos);

ConsumePendingCallback(java_baton, frame_time, target_time);
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
ConsumePendingCallback(weak_this, frame_time, target_time);
}

// static
void VsyncWaiterAndroid::ConsumePendingCallback(
jlong java_baton,
std::weak_ptr<VsyncWaiter>* weak_this,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
auto shared_this = weak_this->lock();
delete weak_this;

Expand All @@ -68,13 +96,27 @@ void VsyncWaiterAndroid::ConsumePendingCallback(
}
}

// static
void VsyncWaiterAndroid::OnUpdateRefreshRate(JNIEnv* env,
jclass jcaller,
jfloat refresh_rate) {
FML_DCHECK(refresh_rate > 0);
g_refresh_rate_ = static_cast<uint>(refresh_rate);
}

// static
bool VsyncWaiterAndroid::Register(JNIEnv* env) {
static const JNINativeMethod methods[] = {{
.name = "nativeOnVsync",
.signature = "(JJJ)V",
.fnPtr = reinterpret_cast<void*>(&OnNativeVsync),
}};
static const JNINativeMethod methods[] = {
{
.name = "nativeOnVsync",
.signature = "(JJJ)V",
.fnPtr = reinterpret_cast<void*>(&OnVsyncFromJava),
},
{
.name = "nativeUpdateRefreshRate",
.signature = "(F)V",
.fnPtr = reinterpret_cast<void*>(&OnUpdateRefreshRate),
}};

jclass clazz = env->FindClass("io/flutter/embedding/engine/FlutterJNI");

Expand All @@ -83,12 +125,10 @@ bool VsyncWaiterAndroid::Register(JNIEnv* env) {
}

g_vsync_waiter_class = new fml::jni::ScopedJavaGlobalRef<jclass>(env, clazz);

FML_CHECK(!g_vsync_waiter_class->is_null());

g_async_wait_for_vsync_method_ = env->GetStaticMethodID(
g_vsync_waiter_class->obj(), "asyncWaitForVsync", "(J)V");

FML_CHECK(g_async_wait_for_vsync_method_ != nullptr);

return env->RegisterNatives(clazz, methods, fml::size(methods)) == 0;
Expand Down
21 changes: 15 additions & 6 deletions shell/platform/android/vsync_waiter_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

namespace flutter {

class AndroidChoreographer;

class VsyncWaiterAndroid final : public VsyncWaiter {
public:
static bool Register(JNIEnv* env);
Expand All @@ -26,16 +28,23 @@ class VsyncWaiterAndroid final : public VsyncWaiter {
// |VsyncWaiter|
void AwaitVSync() override;

static void OnNativeVsync(JNIEnv* env,
jclass jcaller,
jlong frameDelayNanos,
jlong refreshPeriodNanos,
jlong java_baton);
static void OnVsyncFromNDK(int64_t frame_nanos, void* data);

static void OnVsyncFromJava(JNIEnv* env,
jclass jcaller,
jlong frameDelayNanos,
jlong refreshPeriodNanos,
jlong java_baton);

static void ConsumePendingCallback(jlong java_baton,
static void ConsumePendingCallback(std::weak_ptr<VsyncWaiter>* weak_this,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time);

static void OnUpdateRefreshRate(JNIEnv* env,
jclass jcaller,
jfloat refresh_rate);

const bool use_ndk_choreographer_;
FML_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterAndroid);
};

Expand Down