1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 #include <android/choreographer.h>
19 #include <android/log.h>
20 #include <android/surface_control_jni.h>
21 #include <jni.h>
22 #include <private/surface_control_private.h>
23 #include <time.h>
24 #include <utils/Log.h>
25 #include <utils/Mutex.h>
26 
27 #include <chrono>
28 #include <cmath>
29 #include <condition_variable>
30 #include <mutex>
31 #include <thread>
32 
33 #undef LOG_TAG
34 #define LOG_TAG "AttachedChoreographerNativeTest"
35 
36 // Copied from cts/tests/tests/view/jni/jniAssert.h, to be removed when integrated in CTS.
37 #define ASSERT(condition, format, args...) \
38     if (!(condition)) {                    \
39         fail(env, format, ##args);         \
40         return;                            \
41     }
42 
43 using namespace std::chrono_literals;
44 
45 static constexpr std::chrono::nanoseconds kMaxRuntime{1s};
46 static constexpr float kFpsTolerance = 5.0f;
47 
48 static constexpr int kNumOfFrames = 20;
49 
50 struct {
51     struct {
52         jclass clazz;
53         jmethodID endTest;
54     } attachedChoreographerNativeTest;
55 } gJni;
56 
57 struct CallbackData {
58     std::mutex mutex;
59 
60     // Condition to signal callbacks are done running and test can be verified.
61     std::condition_variable_any condition;
62 
63     // Flag to ensure not to lock on the condition if notify is called before wait_for.
64     bool callbacksComplete = false;
65 
66     AChoreographer* choreographer = nullptr;
GUARDED_BYCallbackData67     int count GUARDED_BY(mutex){0};
GUARDED_BYCallbackData68     std::chrono::nanoseconds frameTime GUARDED_BY(mutex){0};
69     std::chrono::nanoseconds startTime;
GUARDED_BYCallbackData70     std::chrono::nanoseconds endTime GUARDED_BY(mutex){0};
71 };
72 
now()73 static std::chrono::nanoseconds now() {
74     return std::chrono::steady_clock::now().time_since_epoch();
75 }
76 
vsyncCallback(const AChoreographerFrameCallbackData * callbackData,void * data)77 static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
78     ALOGI("%s: Vsync callback running", __func__);
79     long frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData);
80 
81     auto* cb = static_cast<CallbackData*>(data);
82     {
83         std::lock_guard<std::mutex> _l(cb->mutex);
84         cb->count++;
85         cb->endTime = now();
86         cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
87 
88         ALOGI("%s: ran callback now %ld, frameTimeNanos %ld, new count %d", __func__,
89               static_cast<long>(cb->endTime.count()), frameTimeNanos, cb->count);
90         if (cb->endTime - cb->startTime > kMaxRuntime) {
91             cb->callbacksComplete = true;
92             cb->condition.notify_all();
93             return;
94         }
95     }
96 
97     ALOGI("%s: Posting next callback", __func__);
98     AChoreographer_postVsyncCallback(cb->choreographer, vsyncCallback, data);
99 }
100 
fail(JNIEnv * env,const char * format,...)101 static void fail(JNIEnv* env, const char* format, ...) {
102     va_list args;
103 
104     va_start(args, format);
105     char* msg;
106     int rc = vasprintf(&msg, format, args);
107     va_end(args);
108 
109     jclass exClass;
110     const char* className = "java/lang/AssertionError";
111     exClass = env->FindClass(className);
112     env->ThrowNew(exClass, msg);
113     free(msg);
114 }
115 
SurfaceControl_getChoreographer(JNIEnv * env,jclass,jobject surfaceControlObj)116 jlong SurfaceControl_getChoreographer(JNIEnv* env, jclass, jobject surfaceControlObj) {
117     return reinterpret_cast<jlong>(
118             ASurfaceControl_getChoreographer(ASurfaceControl_fromJava(env, surfaceControlObj)));
119 }
120 
frameRateEquals(float fr1,float fr2)121 static bool frameRateEquals(float fr1, float fr2) {
122     return std::abs(fr1 - fr2) <= kFpsTolerance;
123 }
124 
endTest(JNIEnv * env,jobject clazz)125 static void endTest(JNIEnv* env, jobject clazz) {
126     env->CallVoidMethod(clazz, gJni.attachedChoreographerNativeTest.endTest);
127 }
128 
android_view_ChoreographerNativeTest_testPostVsyncCallbackAtFrameRate(JNIEnv * env,jobject clazz,jlong choreographerPtr,jfloat expectedFrameRate)129 static void android_view_ChoreographerNativeTest_testPostVsyncCallbackAtFrameRate(
130         JNIEnv* env, jobject clazz, jlong choreographerPtr, jfloat expectedFrameRate) {
131     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
132     CallbackData cb;
133     cb.choreographer = choreographer;
134     cb.startTime = now();
135     ALOGI("%s: Post first callback at %ld", __func__, static_cast<long>(cb.startTime.count()));
136     AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb);
137 
138     std::scoped_lock<std::mutex> conditionLock(cb.mutex);
139     ASSERT(cb.condition.wait_for(cb.mutex, 2 * kMaxRuntime, [&cb] { return cb.callbacksComplete; }),
140            "Never received callbacks!");
141 
142     float actualFrameRate = static_cast<float>(cb.count) /
143             (static_cast<double>((cb.endTime - cb.startTime).count()) / 1'000'000'000.0);
144     ALOGI("%s: callback called %d times with final start time %ld, end time %ld, effective "
145           "frame rate %f",
146           __func__, cb.count, static_cast<long>(cb.startTime.count()),
147           static_cast<long>(cb.endTime.count()), actualFrameRate);
148     ASSERT(frameRateEquals(actualFrameRate, expectedFrameRate),
149            "Effective frame rate is %f but expected to be %f", actualFrameRate, expectedFrameRate);
150 
151     endTest(env, clazz);
152 }
153 
154 static JNINativeMethod gMethods[] = {
155         {"nativeSurfaceControl_getChoreographer", "(Landroid/view/SurfaceControl;)J",
156          (void*)SurfaceControl_getChoreographer},
157         {"nativeTestPostVsyncCallbackAtFrameRate", "(JF)V",
158          (void*)android_view_ChoreographerNativeTest_testPostVsyncCallbackAtFrameRate},
159 };
160 
register_android_android_view_tests_ChoreographerNativeTest(JNIEnv * env)161 int register_android_android_view_tests_ChoreographerNativeTest(JNIEnv* env) {
162     jclass clazz =
163             env->FindClass("android/view/choreographertests/AttachedChoreographerNativeTest");
164     gJni.attachedChoreographerNativeTest.clazz = static_cast<jclass>(env->NewGlobalRef(clazz));
165     gJni.attachedChoreographerNativeTest.endTest = env->GetMethodID(clazz, "endTest", "()V");
166     return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
167 }
168