blob: ab4ce5852f915c5366b78e6b510988d26c4026bb [file] [log] [blame]
/*
* 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 <android/choreographer.h>
#include <android/looper.h>
#include <jni.h>
#include <sys/time.h>
#include <time.h>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <set>
#include <sstream>
#include <thread>
#define LOG_TAG "ChoreographerNative"
#define ASSERT(condition, format, args...) \
if (!(condition)) { \
fail(env, format, ## args); \
return; \
}
using namespace std::chrono_literals;
static constexpr std::chrono::nanoseconds NOMINAL_VSYNC_PERIOD{16ms};
static constexpr std::chrono::nanoseconds DELAY_PERIOD{NOMINAL_VSYNC_PERIOD * 5};
static constexpr std::chrono::nanoseconds ZERO{std::chrono::nanoseconds::zero()};
static std::mutex gLock;
static std::set<int64_t> gSupportedRefreshPeriods;
struct Callback {
Callback(const char* name): name(name) {}
const char* name;
int count{0};
std::chrono::nanoseconds frameTime{0LL};
};
struct RefreshRateCallback {
RefreshRateCallback(const char* name): name(name) {}
const char* name;
int count{0};
std::chrono::nanoseconds vsyncPeriod{0LL};
};
static void frameCallback64(int64_t frameTimeNanos, void* data) {
std::lock_guard<std::mutex> _l(gLock);
Callback* cb = static_cast<Callback*>(data);
cb->count++;
cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
}
static void frameCallback(long frameTimeNanos, void* data) {
frameCallback64((int64_t) frameTimeNanos, data);
}
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 std::chrono::nanoseconds now() {
return std::chrono::steady_clock::now().time_since_epoch();
}
static void fail(JNIEnv* env, const char* format, ...) {
va_list args;
va_start(args, format);
char *msg;
int rc = vasprintf(&msg, format, args);
va_end(args);
jclass exClass;
const char *className = "java/lang/AssertionError";
exClass = env->FindClass(className);
env->ThrowNew(exClass, msg);
free(msg);
}
static void verifyCallback(JNIEnv* env, Callback* cb, int expectedCount,
std::chrono::nanoseconds startTime, std::chrono::nanoseconds maxTime) {
std::lock_guard<std::mutex> _l{gLock};
ASSERT(cb->count == expectedCount, "Choreographer failed to invoke '%s' %d times - actual: %d",
cb->name, expectedCount, cb->count);
if (maxTime > ZERO) {
auto duration = cb->frameTime - startTime;
ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
cb->name, expectedCount);
}
}
static std::string dumpSupportedRefreshPeriods() {
std::stringstream ss;
ss << "{ ";
for (const long& period : gSupportedRefreshPeriods) {
ss << period << ",";
}
ss << "}";
return ss.str();
}
static void verifyRefreshRateCallback(JNIEnv* env, RefreshRateCallback* 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, 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 suppoted periods.
ASSERT(cb->vsyncPeriod > ZERO,
"Choreographer failed to report a nonzero refresh period invoking '%s'",
cb->name);
ASSERT(gSupportedRefreshPeriods.count(cb->vsyncPeriod.count()) > 0,
"Choreographer failed to report a supported refresh period invoking '%s': supported periods: %s, actual: %lu",
cb->name, 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_cts_ChoreographerNativeTest_getChoreographer(JNIEnv*, jclass) {
std::lock_guard<std::mutex> _l{gLock};
return reinterpret_cast<jlong>(AChoreographer_getInstance());
}
static jboolean android_view_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_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
Callback* cb1 = new Callback("cb1");
Callback* cb2 = new Callback("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_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
Callback* cb1 = new 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_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
Callback* cb1 = new Callback("cb1");
Callback* cb2 = new Callback("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_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 = new Callback("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_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
Callback* cb1 = new Callback("cb1");
Callback* cb64 = new Callback("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_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
Callback* cb1 = new Callback("cb1");
Callback* cb64 = new Callback("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_cts_ChoreographerNativeTest_testRefreshRateCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
RefreshRateCallback* cb = new RefreshRateCallback("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(env, cb, 1);
AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
}
static void android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
RefreshRateCallback* cb2 = new RefreshRateCallback("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(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(env, cb1, 0);
verifyRefreshRateCallback(env, cb2, 1);
AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
}
static void android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
RefreshRateCallback* cb2 = new RefreshRateCallback("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(env, cb1, 1);
verifyRefreshRateCallback(env, cb2, 1);
AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb1);
AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb2);
}
static void android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
RefreshRateCallback* cb1 = new RefreshRateCallback("cb1");
RefreshRateCallback* cb2 = new RefreshRateCallback("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(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(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_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks(
JNIEnv* env, jclass, jlong choreographerPtr) {
AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
RefreshRateCallback* cb = new RefreshRateCallback("cb");
AChoreographer_registerRefreshRateCallback(choreographer, refreshRateCallback, cb);
Callback* cb1 = new Callback("cb1");
Callback* cb64 = new Callback("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(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10);
ALooper_pollAll(16, nullptr, nullptr, nullptr);
verifyRefreshRateCallback(env, cb, 1);
verifyCallback(env, cb64, 1, start, DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11);
const auto delayToTestFor32Bit =
sizeof(long) == sizeof(int64_t) ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11 : ZERO;
verifyCallback(env, cb1, 1, start, delayToTestFor32Bit);
AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
}
static JNINativeMethod gMethods[] = {
{ "nativeGetChoreographer", "()J",
(void *) android_view_cts_ChoreographerNativeTest_getChoreographer},
{ "nativePrepareChoreographerTests", "(J[J)Z",
(void *) android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
{ "nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
{ "nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallback64WithDelayEventuallyRunsCallback},
{ "nativeTestPostCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithoutDelayEventuallyRunsCallback},
{ "nativeTestPostCallbackWithDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallbackWithDelayEventuallyRunsCallback},
{ "nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithoutDelayEventuallyRunsCallback},
{ "nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testPostCallbackMixedWithDelayEventuallyRunsCallback},
{ "nativeTestRefreshRateCallback", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallback},
{ "nativeTestUnregisteringRefreshRateCallback", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testUnregisteringRefreshRateCallback},
{ "nativeTestMultipleRefreshRateCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testMultipleRefreshRateCallbacks},
{ "nativeTestAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testAttemptToAddRefreshRateCallbackTwiceDoesNotAddTwice},
{ "nativeTestRefreshRateCallbackMixedWithFrameCallbacks", "(J)V",
(void *) android_view_cts_ChoreographerNativeTest_testRefreshRateCallbackMixedWithFrameCallbacks},
};
int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env)
{
jclass clazz = env->FindClass("android/view/cts/ChoreographerNativeTest");
return env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(JNINativeMethod));
}