| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include <ChoreographerTestUtils.h> |
| #include <android/choreographer.h> |
| #include <android/looper.h> |
| #include <android/log.h> |
| #include <jni.h> |
| #include <sys/time.h> |
| #include <time.h> |
| |
| #include <chrono> |
| #include <cmath> |
| #include <cstdlib> |
| #include <cstring> |
| #include <limits> |
| #include <mutex> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <thread> |
| #include <tuple> |
| #include <vector> |
| |
| #define LOG_TAG "ChoreographerNativeTest" |
| #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) |
| |
| using namespace std::chrono_literals; |
| |
| struct { |
| struct { |
| jclass clazz; |
| jmethodID checkRefreshRateIsCurrentAndSwitch; |
| } choreographerNativeTest; |
| } gJni; |
| |
| static std::set<int64_t> gSupportedRefreshPeriods; |
| |
| struct RefreshRateCallback { |
| RefreshRateCallback(const char* name): name(name) {} |
| std::string name; |
| int count{0}; |
| std::chrono::nanoseconds vsyncPeriod{0LL}; |
| }; |
| |
| struct RefreshRateCallbackWithDisplayManager { |
| RefreshRateCallbackWithDisplayManager(const char* name, JNIEnv* env, jobject clazz) |
| : name(name), env(env), clazz(clazz) {} |
| std::string name; |
| JNIEnv* env; |
| jobject clazz; |
| int count{0}; |
| std::chrono::nanoseconds vsyncPeriod{0LL}; |
| }; |
| |
| static void refreshRateCallback(int64_t vsyncPeriodNanos, void* data) { |
| std::lock_guard<std::mutex> _l(gLock); |
| RefreshRateCallback* cb = static_cast<RefreshRateCallback*>(data); |
| cb->count++; |
| cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos}; |
| } |
| |
| static void refreshRateCallbackWithDisplayManager(int64_t vsyncPeriodNanos, void* data) { |
| std::lock_guard<std::mutex> _l(gLock); |
| RefreshRateCallbackWithDisplayManager* cb = |
| static_cast<RefreshRateCallbackWithDisplayManager*>(data); |
| cb->count++; |
| cb->vsyncPeriod = std::chrono::nanoseconds{vsyncPeriodNanos}; |
| cb->env->CallVoidMethod(cb->clazz, |
| gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch, |
| static_cast<int>(std::round(1e9f / cb->vsyncPeriod.count()))); |
| } |
| |
| static std::string dumpSupportedRefreshPeriods() { |
| std::stringstream ss; |
| ss << "{ "; |
| for (const long& period : gSupportedRefreshPeriods) { |
| ss << period << ","; |
| } |
| ss << "}"; |
| return ss.str(); |
| } |
| |
| template <class T> |
| static void verifyRefreshRateCallback(JNIEnv* env, const T& cb, int expectedMin) { |
| std::lock_guard<std::mutex> _l(gLock); |
| ASSERT(cb.count >= expectedMin, "Choreographer failed to invoke '%s' %d times - actual: %d", |
| cb.name.c_str(), expectedMin, cb.count); |
| // Unfortunately we can't verify the specific vsync period as public apis |
| // don't provide a guarantee that we adhere to a particular refresh rate. |
| // The best we can do is check that the reported period is contained in the |
| // set of supported periods. |
| ASSERT(cb.vsyncPeriod > ZERO, |
| "Choreographer failed to report a nonzero refresh period invoking '%s'", |
| cb.name.c_str()); |
| ASSERT(gSupportedRefreshPeriods.count(cb.vsyncPeriod.count()) > 0, |
| "Choreographer failed to report a supported refresh period invoking '%s': supported " |
| "periods: %s, actual: %lu", |
| cb.name.c_str(), dumpSupportedRefreshPeriods().c_str(), cb.vsyncPeriod.count()); |
| } |
| |
| static void resetRefreshRateCallback(RefreshRateCallback& cb) { |
| std::lock_guard<std::mutex> _l(gLock); |
| cb.count = 0; |
| } |
| |
| static jlong android_view_surfacecontrol_cts_ChoreographerNativeTest_getChoreographer(JNIEnv*, jclass) { |
| std::lock_guard<std::mutex> _l{gLock}; |
| return reinterpret_cast<jlong>(AChoreographer_getInstance()); |
| } |
| |
| static jboolean android_view_surfacecontrol_cts_ChoreographerNativeTest_prepareChoreographerTests(JNIEnv* env, jclass, |
| jlong choreographerPtr, jlongArray supportedRefreshPeriods) { |
| std::lock_guard<std::mutex> _l{gLock}; |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| const size_t count = env->GetArrayLength(supportedRefreshPeriods); |
| const jlong* vals = env->GetLongArrayElements(supportedRefreshPeriods, nullptr); |
| for (size_t i = 0; i < count; ++i) { |
| gSupportedRefreshPeriods.insert(vals[i]); |
| } |
| env->ReleaseLongArrayElements(supportedRefreshPeriods, const_cast<jlong*>(vals), JNI_ABORT); |
| return choreographer != nullptr; |
| } |
| |
| static void |
| android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostVsyncCallbackWithoutDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| VsyncCallback cb1("cb1", env); |
| VsyncCallback cb2("cb2", env); |
| auto start = now(); |
| |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb2); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb2, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| { |
| std::lock_guard<std::mutex> _l{gLock}; |
| auto delta = cb2.frameTime - cb1.frameTime; |
| ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2, |
| "Callback 1 and 2 have frame times too large of a delta in frame times"); |
| } |
| |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| start = now(); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb2, 1, start, ZERO); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| VsyncCallback cb1("cb1", env); |
| auto start = now(); |
| |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| std::lock_guard<std::mutex> _l{gLock}; |
| for (const VsyncCallback::FrameTime& frameTime : cb1.getTimeline()) { |
| int64_t vsyncId = frameTime.vsyncId; |
| ASSERT(vsyncId >= 0, "Invalid vsync ID"); |
| ASSERT(std::count_if(cb1.getTimeline().begin(), cb1.getTimeline().end(), |
| [vsyncId](const VsyncCallback::FrameTime& ft) { |
| return ft.vsyncId == vsyncId; |
| }) == 1, |
| "Vsync ID is not unique"); |
| } |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| VsyncCallback cb1("cb1", env); |
| auto start = now(); |
| |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| std::lock_guard<std::mutex> _l{gLock}; |
| std::vector<VsyncCallback::FrameTime> frameTimelines = cb1.getTimeline(); |
| ALOGD("Test start time = %lld", std::chrono::nanoseconds{start}.count()); |
| ALOGD("VsyncCallback frameTime = %lld", cb1.frameTime.count()); |
| for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < frameTimelines.size(); i++) { |
| auto deadline = std::chrono::nanoseconds{frameTimelines[i].deadline}; |
| ALOGD("\tframe timeline #%d: deadline = %lld", i, deadline.count()); |
| ASSERT(deadline > std::chrono::nanoseconds{start}, |
| "Deadline (%lld) must be after start time (%lld)", deadline.count(), |
| std::chrono::nanoseconds{start}.count()); |
| ASSERT(deadline > cb1.frameTime, "Deadline (%lld) must be after frame time (%lld)", |
| deadline.count(), cb1.frameTime.count()); |
| ASSERT(deadline > lastValue, |
| "Deadline (%lld) must be greater than last frame deadline (%lld)", deadline.count(), |
| lastValue.count()); |
| lastValue = deadline; |
| } |
| // To avoid API fragmentation, enforce there are at least a certain amount of frame timeline |
| // choices, by number of choices or by the last deadline timestamp. |
| auto lastDeadline = std::chrono::nanoseconds{frameTimelines[frameTimelines.size() - 1].deadline}; |
| auto timeDelta = lastDeadline - start; |
| const auto threshold = std::chrono::nanoseconds{45ms}; |
| ASSERT(timeDelta > threshold, |
| "Not enough later choices for frame timelines. " |
| "Time delta between start and latest deadline (%lld) must be larger than the threshold " |
| "(%lld)", |
| timeDelta.count(), threshold.count()); |
| } |
| |
| static void |
| android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| VsyncCallback cb1("cb1", env); |
| auto start = now(); |
| |
| AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb1); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| std::lock_guard<std::mutex> _l{gLock}; |
| for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < cb1.getTimeline().size(); i++) { |
| auto expectedPresentTime = |
| std::chrono::nanoseconds(cb1.getTimeline()[i].expectedPresentTime); |
| auto deadline = std::chrono::nanoseconds(cb1.getTimeline()[i].deadline); |
| ASSERT(expectedPresentTime > cb1.frameTime, |
| "Expected present time must be after frame time"); |
| ASSERT(expectedPresentTime > deadline, "Expected present time must be after deadline"); |
| ASSERT(expectedPresentTime > lastValue, |
| "Expected present time must be greater than last frame expected present time"); |
| lastValue = expectedPresentTime; |
| } |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1("cb1"); |
| Callback cb2("cb2"); |
| auto start = now(); |
| |
| AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1); |
| AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb2); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb2, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| { |
| std::lock_guard<std::mutex> _l{gLock}; |
| auto delta = cb2.frameTime - cb1.frameTime; |
| ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2, |
| "Callback 1 and 2 have frame times too large of a delta in frame times"); |
| } |
| |
| AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb1); |
| start = now(); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb2, 1, start, ZERO); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1 = Callback("cb1"); |
| auto start = now(); |
| |
| auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb1, delay); |
| |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 0, start, ZERO); |
| |
| std::this_thread::sleep_for(DELAY_PERIOD); |
| verifyCallback(env, cb1, 1, start, DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 3); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1("cb1"); |
| Callback cb2("cb2"); |
| auto start = now(); |
| const auto delay = NOMINAL_VSYNC_PERIOD * 3; |
| // Delay calculations are known to be broken on 32-bit systems (overflow), |
| // so we skip testing the delay on such systems by setting this to ZERO. |
| const auto delayToTest = sizeof(long) == sizeof(int64_t) ? delay : ZERO; |
| |
| AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1); |
| AChoreographer_postFrameCallback(choreographer, frameCallback, &cb2); |
| std::this_thread::sleep_for(delay); |
| |
| verifyCallback(env, cb1, 1, start, delayToTest); |
| verifyCallback(env, cb2, 1, start, delayToTest); |
| |
| // This delta can only be reliably calculated on 64-bit systems. We skip this |
| // part of the test on systems known to be broken. |
| if (sizeof(long) == sizeof(int64_t)) { |
| std::lock_guard<std::mutex> _l{gLock}; |
| auto delta = cb2.frameTime - cb1.frameTime; |
| ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2, |
| "Callback 1 and 2 have frame times too large of a delta in frame times"); |
| } |
| |
| AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1); |
| start = now(); |
| std::this_thread::sleep_for(delay); |
| |
| verifyCallback(env, cb1, 2, start, delayToTest); |
| verifyCallback(env, cb2, 1, start, ZERO); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| if (sizeof(long) != sizeof(int64_t)) { |
| // skip test for known broken states. |
| return; |
| } |
| |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1("cb1"); |
| auto start = now(); |
| |
| auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay); |
| |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 0, start, ZERO); |
| |
| std::this_thread::sleep_for(DELAY_PERIOD); |
| const auto delayToTest = |
| sizeof(long) == sizeof(int64_t) ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 3 : ZERO; |
| verifyCallback(env, cb1, 1, start, delayToTest); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1("cb1"); |
| Callback cb64("cb64"); |
| auto start = now(); |
| |
| AChoreographer_postFrameCallback(choreographer, frameCallback, &cb1); |
| AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| |
| verifyCallback(env, cb1, 1, start, ZERO); |
| verifyCallback(env, cb64, 1, start, NOMINAL_VSYNC_PERIOD * 3); |
| |
| // This delta can only be reliably calculated on 64-bit systems. We skip this |
| // part of the test on systems known to be broken. |
| if (sizeof(long) == sizeof(int64_t)) { |
| std::lock_guard<std::mutex> _l{gLock}; |
| auto delta = cb64.frameTime - cb1.frameTime; |
| ASSERT(delta == ZERO || delta > ZERO && delta < NOMINAL_VSYNC_PERIOD * 2, |
| "Callback 1 and 2 have frame times too large of a delta in frame times"); |
| } |
| |
| AChoreographer_postFrameCallback64(choreographer, frameCallback64, &cb64); |
| start = now(); |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 1, start, ZERO); |
| verifyCallback(env, cb64, 2, start, NOMINAL_VSYNC_PERIOD * 3); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| Callback cb1("cb1"); |
| Callback cb64("cb64"); |
| auto start = now(); |
| |
| auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay); |
| AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay); |
| |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3); |
| verifyCallback(env, cb1, 0, start, ZERO); |
| verifyCallback(env, cb64, 0, start, ZERO); |
| |
| std::this_thread::sleep_for(DELAY_PERIOD); |
| verifyCallback(env, cb64, 1, start, DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 3); |
| const auto delayToTestFor32Bit = |
| sizeof(long) == sizeof(int64_t) ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 3 : ZERO; |
| verifyCallback(env, cb1, 1, start, delayToTestFor32Bit); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| RefreshRateCallback cb("cb"); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb); |
| |
| // Give the display system time to push an initial callback. |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1); |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| RefreshRateCallback cb1("cb1"); |
| RefreshRateCallback cb2("cb2"); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| |
| // Give the display system time to push an initial callback. |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1); |
| |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| // Flush out pending callback events for the callback |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| resetRefreshRateCallback(cb1); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| // Verify that cb2 is called on registration, but not cb1. |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1); |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| RefreshRateCallback cb1("cb1"); |
| RefreshRateCallback cb2("cb2"); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| |
| // Give the display system time to push an initial refresh rate change. |
| // Polling the event will allow both callbacks to be triggered. |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb2, 1); |
| |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| } |
| |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| RefreshRateCallback cb1("cb1"); |
| RefreshRateCallback cb2("cb2"); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| |
| // Give the display system time to push an initial callback. |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 1); |
| |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb1); |
| // Flush out pending callback events for the callback |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| resetRefreshRateCallback(cb1); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| // Verify that cb1 is not called again, even thiough it was registered once |
| // and unregistered again |
| std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 10); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb1, 0); |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb2); |
| } |
| |
| // This test must be run on the UI thread for fine-grained control of looper |
| // scheduling. |
| static void android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks( |
| JNIEnv* env, jclass, jlong choreographerPtr) { |
| AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); |
| RefreshRateCallback cb("cb"); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, &cb); |
| |
| Callback cb1("cb1"); |
| Callback cb64("cb64"); |
| auto start = now(); |
| |
| auto vsyncPeriod = std::chrono::duration_cast<std::chrono::milliseconds>( |
| NOMINAL_VSYNC_PERIOD) |
| .count(); |
| auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, &cb1, delay); |
| AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, &cb64, delay); |
| |
| std::this_thread::sleep_for(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10); |
| // Ensure that callbacks are seen by the looper instance at approximately |
| // the same time, and provide enough time for the looper instance to process |
| // the delayed callback and the requested vsync signal if needed. |
| int pollResult; |
| do { |
| pollResult = ALooper_pollOnce(vsyncPeriod * 5, nullptr, nullptr, nullptr); |
| } while (pollResult != ALOOPER_POLL_TIMEOUT && pollResult != ALOOPER_POLL_ERROR); |
| verifyRefreshRateCallback<RefreshRateCallback>(env, cb, 1); |
| verifyCallback(env, cb64, 1, start, |
| DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15); |
| const auto delayToTestFor32Bit = |
| sizeof(long) == sizeof(int64_t) |
| ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15 |
| : ZERO; |
| verifyCallback(env, cb1, 1, start, delayToTestFor32Bit); |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb); |
| } |
| |
| // This test cannot be run on the UI thread because it relies on callbacks to be dispatched on the |
| // application UI thread. |
| static void |
| android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager( |
| JNIEnv* env, jobject clazz) { |
| // Test harness choreographer is not on the main thread, so create a thread-local choreographer |
| // instance. |
| ALooper_prepare(0); |
| AChoreographer* choreographer = AChoreographer_getInstance(); |
| RefreshRateCallbackWithDisplayManager cb("cb", env, clazz); |
| |
| AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallbackWithDisplayManager, |
| &cb); |
| |
| auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count(); |
| |
| const size_t numRuns = 1000; |
| int previousCount = 0; |
| for (int i = 0; i < numRuns; ++i) { |
| const size_t numTries = 5; |
| for (int j = 0; j < numTries; j++) { |
| // In theory we only need to poll once because the test harness configuration should |
| // enforce that we won't get spurious callbacks. In practice, there may still be |
| // spurious callbacks due to hotplug or other display events that aren't suppressed. So |
| // we add some slack by retrying a few times, but we stop at the first refresh rate |
| // callback (1) to keep the test runtime reasonably short, and (2) to keep the test |
| // under better control so that it does not spam the system with refresh rate changes. |
| int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr); |
| ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed on run: %d with error: %d", i, |
| result); |
| if (previousCount != cb.count) { |
| verifyRefreshRateCallback<RefreshRateCallbackWithDisplayManager>(env, cb, |
| previousCount + 1); |
| previousCount = cb.count; |
| break; |
| } |
| |
| ASSERT(j < numTries - 1, "No callback observed for run: %d", i); |
| } |
| } |
| AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, &cb); |
| } |
| |
| static JNINativeMethod gMethods[] = { |
| {"nativeGetChoreographer", "()J", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_getChoreographer}, |
| {"nativePrepareChoreographerTests", "(J[J)Z", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_prepareChoreographerTests}, |
| {"nativeTestPostVsyncCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostVsyncCallbackWithoutDelayEventuallyRunsCallback}, |
| {"nativeTestFrameCallbackDataVsyncIdValid", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid}, |
| {"nativeTestFrameCallbackDataDeadlineInFuture", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture}, |
| {"nativeTestFrameCallbackDataExpectedPresentTimeInFuture", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture}, |
| {"nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback}, |
| {"nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback}, |
| {"nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback}, |
| {"nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback}, |
| {"nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback}, |
| {"nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback}, |
| {"nativeTestRefreshRateCallback", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallback}, |
| {"nativeTestUnregisteringRefreshRateCallback", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback}, |
| {"nativeTestMultipleRefreshRateCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks}, |
| {"nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice}, |
| {"nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks}, |
| {"nativeTestRefreshRateCallbacksAreSyncedWithDisplayManager", "()V", |
| (void*)android_view_surfacecontrol_cts_ChoreographerNativeTest_testRefreshRateCallbacksAreSyncedWithDisplayManager}, |
| }; |
| |
| int register_android_view_surfacecontrol_cts_ChoreographerNativeTest(JNIEnv* env) |
| { |
| jclass clazz = env->FindClass("android/view/surfacecontrol/cts/ChoreographerNativeTest"); |
| gJni.choreographerNativeTest.clazz = static_cast<jclass>(env->NewGlobalRef(clazz)); |
| gJni.choreographerNativeTest.checkRefreshRateIsCurrentAndSwitch = |
| env->GetMethodID(clazz, "checkRefreshRateIsCurrentAndSwitch", "(I)V"); |
| return env->RegisterNatives(clazz, gMethods, |
| sizeof(gMethods) / sizeof(JNINativeMethod)); |
| } |