Skip to content

Commit 266d336

Browse files
Use AChoreographer methods to await vsync when available (flutter#31859)
1 parent 4cb7efa commit 266d336

10 files changed

Lines changed: 198 additions & 32 deletions

File tree

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.h
10931093
FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.cc
10941094
FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.h
10951095
FILE: ../../../flutter/shell/platform/android/AndroidManifest.xml
1096+
FILE: ../../../flutter/shell/platform/android/android_choreographer.cc
1097+
FILE: ../../../flutter/shell/platform/android/android_choreographer.h
10961098
FILE: ../../../flutter/shell/platform/android/android_context_gl.cc
10971099
FILE: ../../../flutter/shell/platform/android/android_context_gl.h
10981100
FILE: ../../../flutter/shell/platform/android/android_context_gl_unittests.cc

shell/platform/android/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ source_set("flutter_shell_native_src") {
6262

6363
sources = [
6464
"$root_build_dir/flutter_icu/icudtl.o",
65+
"android_choreographer.cc",
66+
"android_choreographer.h",
6567
"android_context_gl.cc",
6668
"android_context_gl.h",
6769
"android_display.cc",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/android/android_choreographer.h"
6+
7+
#include "flutter/fml/native_library.h"
8+
9+
// Only avialalbe on API 24+
10+
typedef void AChoreographer;
11+
// Only available on API 29+ or API 24+ if the architecture is 64-bit.
12+
typedef void (*AChoreographer_frameCallback)(int64_t frameTimeNanos,
13+
void* data);
14+
// Only avialalbe on API 24+
15+
typedef AChoreographer* (*AChoreographer_getInstance_FPN)();
16+
typedef void (*AChoreographer_postFrameCallback_FPN)(
17+
AChoreographer* choreographer,
18+
AChoreographer_frameCallback callback,
19+
void* data);
20+
static AChoreographer_getInstance_FPN AChoreographer_getInstance;
21+
static AChoreographer_postFrameCallback_FPN AChoreographer_postFrameCallback;
22+
23+
namespace flutter {
24+
25+
bool AndroidChoreographer::ShouldUseNDKChoreographer() {
26+
static std::optional<bool> use_ndk_choreographer;
27+
if (use_ndk_choreographer) {
28+
return use_ndk_choreographer.value();
29+
}
30+
auto libandroid = fml::NativeLibrary::Create("libandroid.so");
31+
FML_DCHECK(libandroid);
32+
auto get_instance_fn =
33+
libandroid->ResolveFunction<AChoreographer_getInstance_FPN>(
34+
"AChoreographer_getInstance");
35+
auto post_frame_callback_fn =
36+
libandroid->ResolveFunction<AChoreographer_postFrameCallback_FPN>(
37+
"AChoreographer_postFrameCallback64");
38+
#if FML_ARCH_CPU_64_BITS
39+
if (!post_frame_callback_fn) {
40+
post_frame_callback_fn =
41+
libandroid->ResolveFunction<AChoreographer_postFrameCallback_FPN>(
42+
"AChoreographer_postFrameCallback");
43+
}
44+
#endif
45+
if (get_instance_fn && post_frame_callback_fn) {
46+
AChoreographer_getInstance = get_instance_fn.value();
47+
AChoreographer_postFrameCallback = post_frame_callback_fn.value();
48+
use_ndk_choreographer = true;
49+
} else {
50+
use_ndk_choreographer = false;
51+
}
52+
return use_ndk_choreographer.value();
53+
}
54+
55+
void AndroidChoreographer::PostFrameCallback(OnFrameCallback callback,
56+
void* data) {
57+
AChoreographer* choreographer = AChoreographer_getInstance();
58+
AChoreographer_postFrameCallback(choreographer, callback, data);
59+
}
60+
61+
} // namespace flutter
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_
6+
#define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_
7+
8+
#include "flutter/fml/macros.h"
9+
10+
#include <cstdint>
11+
12+
namespace flutter {
13+
14+
//------------------------------------------------------------------------------
15+
/// The Android Choreographer is used by `VsyncWaiterAndroid` to await vsync
16+
/// signal. It's only available on API 29+ or API 24+ if the architecture is
17+
/// 64-bit.
18+
///
19+
class AndroidChoreographer {
20+
public:
21+
typedef void (*OnFrameCallback)(int64_t frame_time_nanos, void* data);
22+
static bool ShouldUseNDKChoreographer();
23+
static void PostFrameCallback(OnFrameCallback callback, void* data);
24+
25+
FML_DISALLOW_COPY_AND_ASSIGN(AndroidChoreographer);
26+
};
27+
28+
} // namespace flutter
29+
30+
#endif // FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CHOREOGRAPHER_H_

shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,18 @@ public void setRefreshRateFPS(float refreshRateFPS) {
257257
// on Android we will need to refactor this. Static lookup makes things a
258258
// bit easier on the C++ side.
259259
FlutterJNI.refreshRateFPS = refreshRateFPS;
260+
updateRefreshRate();
260261
}
261262

263+
public void updateRefreshRate() {
264+
if (!FlutterJNI.loadLibraryCalled) {
265+
return;
266+
}
267+
nativeUpdateRefreshRate(refreshRateFPS);
268+
}
269+
270+
private native void nativeUpdateRefreshRate(float refreshRateFPS);
271+
262272
/**
263273
* The Android vsync waiter implementation in C++ needs to know when a vsync signal arrives, which
264274
* is obtained via Java API. The delegate set here is called on the C++ side when the engine is

shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public InitResult call() {
185185
ResourceExtractor resourceExtractor = initResources(appContext);
186186

187187
flutterJNI.loadLibrary();
188+
flutterJNI.updateRefreshRate();
188189

189190
// Prefetch the default font manager as soon as possible on a background thread.
190191
// It helps to reduce time cost of engine setup that blocks the platform thread.

shell/platform/android/test/io/flutter/embedding/engine/FlutterJNITest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.Assert.assertEquals;
44
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.spy;
56
import static org.mockito.Mockito.times;
67
import static org.mockito.Mockito.verify;
78
import static org.mockito.Mockito.when;
@@ -245,4 +246,13 @@ public void invokePlatformMessageResponseCallback__wantsDirectBuffer() {
245246
ByteBuffer buffer = ByteBuffer.allocate(4);
246247
flutterJNI.invokePlatformMessageResponseCallback(0, buffer, buffer.position());
247248
}
249+
250+
@Test
251+
public void setRefreshRateFPS__callsUpdateRefreshRate() {
252+
FlutterJNI flutterJNI = spy(new FlutterJNI());
253+
// --- Execute Test ---
254+
flutterJNI.setRefreshRateFPS(120.0f);
255+
// --- Verify Results ---
256+
verify(flutterJNI, times(1)).updateRefreshRate();
257+
}
248258
}

shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public void itReportsInitializedAfterInitializing() {
5555
shadowOf(getMainLooper()).idle();
5656
assertTrue(flutterLoader.initialized());
5757
verify(mockFlutterJNI, times(1)).loadLibrary();
58+
verify(mockFlutterJNI, times(1)).updateRefreshRate();
5859
}
5960

6061
@Test

shell/platform/android/vsync_waiter_android.cc

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,81 @@
1313
#include "flutter/fml/platform/android/scoped_java_ref.h"
1414
#include "flutter/fml/size.h"
1515
#include "flutter/fml/trace_event.h"
16+
#include "flutter/shell/platform/android/android_choreographer.h"
1617

1718
namespace flutter {
1819

1920
static fml::jni::ScopedJavaGlobalRef<jclass>* g_vsync_waiter_class = nullptr;
2021
static jmethodID g_async_wait_for_vsync_method_ = nullptr;
22+
static std::atomic_uint g_refresh_rate_ = 60;
2123

2224
VsyncWaiterAndroid::VsyncWaiterAndroid(flutter::TaskRunners task_runners)
23-
: VsyncWaiter(std::move(task_runners)) {}
25+
: VsyncWaiter(std::move(task_runners)),
26+
use_ndk_choreographer_(
27+
AndroidChoreographer::ShouldUseNDKChoreographer()) {}
2428

2529
VsyncWaiterAndroid::~VsyncWaiterAndroid() = default;
2630

2731
// |VsyncWaiter|
2832
void VsyncWaiterAndroid::AwaitVSync() {
29-
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
30-
jlong java_baton = reinterpret_cast<jlong>(weak_this);
31-
32-
task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
33-
JNIEnv* env = fml::jni::AttachCurrentThread();
34-
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), //
35-
g_async_wait_for_vsync_method_, //
36-
java_baton //
37-
);
38-
});
33+
if (use_ndk_choreographer_) {
34+
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
35+
fml::TaskRunner::RunNowOrPostTask(
36+
task_runners_.GetUITaskRunner(), [weak_this]() {
37+
AndroidChoreographer::PostFrameCallback(&OnVsyncFromNDK, weak_this);
38+
});
39+
} else {
40+
// TODO(99798): Remove it when we drop support for API level < 29.
41+
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
42+
jlong java_baton = reinterpret_cast<jlong>(weak_this);
43+
task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
44+
JNIEnv* env = fml::jni::AttachCurrentThread();
45+
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), //
46+
g_async_wait_for_vsync_method_, //
47+
java_baton //
48+
);
49+
});
50+
}
51+
}
52+
53+
// static
54+
void VsyncWaiterAndroid::OnVsyncFromNDK(int64_t frame_nanos, void* data) {
55+
TRACE_EVENT0("flutter", "VSYNC");
56+
57+
auto frame_time = fml::TimePoint::FromEpochDelta(
58+
fml::TimeDelta::FromNanoseconds(frame_nanos));
59+
auto now = fml::TimePoint::Now();
60+
if (frame_time > now) {
61+
frame_time = now;
62+
}
63+
auto target_time = frame_time + fml::TimeDelta::FromNanoseconds(
64+
1000000000.0 / g_refresh_rate_);
65+
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(data);
66+
ConsumePendingCallback(weak_this, frame_time, target_time);
3967
}
4068

4169
// static
42-
void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env,
43-
jclass jcaller,
44-
jlong frameDelayNanos,
45-
jlong refreshPeriodNanos,
46-
jlong java_baton) {
70+
void VsyncWaiterAndroid::OnVsyncFromJava(JNIEnv* env,
71+
jclass jcaller,
72+
jlong frameDelayNanos,
73+
jlong refreshPeriodNanos,
74+
jlong java_baton) {
4775
TRACE_EVENT0("flutter", "VSYNC");
4876

4977
auto frame_time =
5078
fml::TimePoint::Now() - fml::TimeDelta::FromNanoseconds(frameDelayNanos);
5179
auto target_time =
5280
frame_time + fml::TimeDelta::FromNanoseconds(refreshPeriodNanos);
5381

54-
ConsumePendingCallback(java_baton, frame_time, target_time);
82+
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
83+
ConsumePendingCallback(weak_this, frame_time, target_time);
5584
}
5685

5786
// static
5887
void VsyncWaiterAndroid::ConsumePendingCallback(
59-
jlong java_baton,
88+
std::weak_ptr<VsyncWaiter>* weak_this,
6089
fml::TimePoint frame_start_time,
6190
fml::TimePoint frame_target_time) {
62-
auto* weak_this = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(java_baton);
6391
auto shared_this = weak_this->lock();
6492
delete weak_this;
6593

@@ -68,13 +96,27 @@ void VsyncWaiterAndroid::ConsumePendingCallback(
6896
}
6997
}
7098

99+
// static
100+
void VsyncWaiterAndroid::OnUpdateRefreshRate(JNIEnv* env,
101+
jclass jcaller,
102+
jfloat refresh_rate) {
103+
FML_DCHECK(refresh_rate > 0);
104+
g_refresh_rate_ = static_cast<uint>(refresh_rate);
105+
}
106+
71107
// static
72108
bool VsyncWaiterAndroid::Register(JNIEnv* env) {
73-
static const JNINativeMethod methods[] = {{
74-
.name = "nativeOnVsync",
75-
.signature = "(JJJ)V",
76-
.fnPtr = reinterpret_cast<void*>(&OnNativeVsync),
77-
}};
109+
static const JNINativeMethod methods[] = {
110+
{
111+
.name = "nativeOnVsync",
112+
.signature = "(JJJ)V",
113+
.fnPtr = reinterpret_cast<void*>(&OnVsyncFromJava),
114+
},
115+
{
116+
.name = "nativeUpdateRefreshRate",
117+
.signature = "(F)V",
118+
.fnPtr = reinterpret_cast<void*>(&OnUpdateRefreshRate),
119+
}};
78120

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

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

85127
g_vsync_waiter_class = new fml::jni::ScopedJavaGlobalRef<jclass>(env, clazz);
86-
87128
FML_CHECK(!g_vsync_waiter_class->is_null());
88129

89130
g_async_wait_for_vsync_method_ = env->GetStaticMethodID(
90131
g_vsync_waiter_class->obj(), "asyncWaitForVsync", "(J)V");
91-
92132
FML_CHECK(g_async_wait_for_vsync_method_ != nullptr);
93133

94134
return env->RegisterNatives(clazz, methods, fml::size(methods)) == 0;

shell/platform/android/vsync_waiter_android.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace flutter {
1616

17+
class AndroidChoreographer;
18+
1719
class VsyncWaiterAndroid final : public VsyncWaiter {
1820
public:
1921
static bool Register(JNIEnv* env);
@@ -26,16 +28,23 @@ class VsyncWaiterAndroid final : public VsyncWaiter {
2628
// |VsyncWaiter|
2729
void AwaitVSync() override;
2830

29-
static void OnNativeVsync(JNIEnv* env,
30-
jclass jcaller,
31-
jlong frameDelayNanos,
32-
jlong refreshPeriodNanos,
33-
jlong java_baton);
31+
static void OnVsyncFromNDK(int64_t frame_nanos, void* data);
32+
33+
static void OnVsyncFromJava(JNIEnv* env,
34+
jclass jcaller,
35+
jlong frameDelayNanos,
36+
jlong refreshPeriodNanos,
37+
jlong java_baton);
3438

35-
static void ConsumePendingCallback(jlong java_baton,
39+
static void ConsumePendingCallback(std::weak_ptr<VsyncWaiter>* weak_this,
3640
fml::TimePoint frame_start_time,
3741
fml::TimePoint frame_target_time);
3842

43+
static void OnUpdateRefreshRate(JNIEnv* env,
44+
jclass jcaller,
45+
jfloat refresh_rate);
46+
47+
const bool use_ndk_choreographer_;
3948
FML_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterAndroid);
4049
};
4150

0 commit comments

Comments
 (0)