You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
11 KiB
300 lines
11 KiB
/*
|
|
* Copyright 2019 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.
|
|
*/
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "LibSurfaceFlingerUnittests"
|
|
#define LOG_NDEBUG 0
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <log/log.h>
|
|
|
|
#include "AsyncCallRecorder.h"
|
|
#include "Scheduler/DispSyncSource.h"
|
|
#include "Scheduler/VSyncDispatch.h"
|
|
|
|
namespace android {
|
|
namespace {
|
|
|
|
using namespace std::chrono_literals;
|
|
using namespace testing;
|
|
|
|
class MockVSyncDispatch : public scheduler::VSyncDispatch {
|
|
public:
|
|
MOCK_METHOD2(registerCallback,
|
|
CallbackToken(std::function<void(nsecs_t, nsecs_t, nsecs_t)> const&, std::string));
|
|
MOCK_METHOD1(unregisterCallback, void(CallbackToken));
|
|
MOCK_METHOD2(schedule, scheduler::ScheduleResult(CallbackToken, ScheduleTiming));
|
|
MOCK_METHOD1(cancel, scheduler::CancelResult(CallbackToken token));
|
|
MOCK_CONST_METHOD1(dump, void(std::string&));
|
|
|
|
MockVSyncDispatch() {
|
|
ON_CALL(*this, registerCallback)
|
|
.WillByDefault(
|
|
[this](std::function<void(nsecs_t, nsecs_t, nsecs_t)> const& callback,
|
|
std::string) {
|
|
CallbackToken token(mNextToken);
|
|
mNextToken++;
|
|
|
|
mCallbacks.emplace(token, CallbackData(callback));
|
|
ALOGD("registerCallback: %zu", token.value());
|
|
return token;
|
|
});
|
|
|
|
ON_CALL(*this, unregisterCallback).WillByDefault([this](CallbackToken token) {
|
|
ALOGD("unregisterCallback: %zu", token.value());
|
|
mCallbacks.erase(token);
|
|
});
|
|
|
|
ON_CALL(*this, schedule).WillByDefault([this](CallbackToken token, ScheduleTiming timing) {
|
|
ALOGD("schedule: %zu", token.value());
|
|
if (mCallbacks.count(token) == 0) {
|
|
ALOGD("schedule: callback %zu not registered", token.value());
|
|
return scheduler::ScheduleResult{};
|
|
}
|
|
|
|
auto& callback = mCallbacks.at(token);
|
|
callback.scheduled = true;
|
|
callback.vsyncTime = timing.earliestVsync;
|
|
callback.targetWakeupTime =
|
|
timing.earliestVsync - timing.workDuration - timing.readyDuration;
|
|
ALOGD("schedule: callback %zu scheduled", token.value());
|
|
return scheduler::ScheduleResult{callback.targetWakeupTime};
|
|
});
|
|
|
|
ON_CALL(*this, cancel).WillByDefault([this](CallbackToken token) {
|
|
ALOGD("cancel: %zu", token.value());
|
|
if (mCallbacks.count(token) == 0) {
|
|
ALOGD("cancel: callback %zu is not registered", token.value());
|
|
return scheduler::CancelResult::Error;
|
|
}
|
|
|
|
auto& callback = mCallbacks.at(token);
|
|
callback.scheduled = false;
|
|
ALOGD("cancel: callback %zu cancelled", token.value());
|
|
return scheduler::CancelResult::Cancelled;
|
|
});
|
|
}
|
|
|
|
void triggerCallbacks() {
|
|
ALOGD("triggerCallbacks");
|
|
for (auto& [token, callback] : mCallbacks) {
|
|
if (callback.scheduled) {
|
|
ALOGD("triggerCallbacks: callback %zu", token.value());
|
|
callback.scheduled = false;
|
|
callback.func(callback.vsyncTime, callback.targetWakeupTime, callback.readyTime);
|
|
} else {
|
|
ALOGD("triggerCallbacks: callback %zu is not scheduled", token.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct CallbackData {
|
|
explicit CallbackData(std::function<void(nsecs_t, nsecs_t, nsecs_t)> func)
|
|
: func(std::move(func)) {}
|
|
|
|
std::function<void(nsecs_t, nsecs_t, nsecs_t)> func;
|
|
bool scheduled = false;
|
|
nsecs_t vsyncTime = 0;
|
|
nsecs_t targetWakeupTime = 0;
|
|
nsecs_t readyTime = 0;
|
|
};
|
|
|
|
std::unordered_map<CallbackToken, CallbackData> mCallbacks;
|
|
size_t mNextToken;
|
|
};
|
|
|
|
class DispSyncSourceTest : public testing::Test, private VSyncSource::Callback {
|
|
protected:
|
|
DispSyncSourceTest();
|
|
~DispSyncSourceTest() override;
|
|
|
|
void createDispSync();
|
|
void createDispSyncSource();
|
|
|
|
void onVSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
|
|
nsecs_t deadlineTimestamp) override;
|
|
|
|
std::unique_ptr<MockVSyncDispatch> mVSyncDispatch;
|
|
std::unique_ptr<scheduler::DispSyncSource> mDispSyncSource;
|
|
|
|
AsyncCallRecorder<void (*)(nsecs_t, nsecs_t, nsecs_t)> mVSyncEventCallRecorder;
|
|
|
|
static constexpr std::chrono::nanoseconds mWorkDuration = 20ms;
|
|
static constexpr std::chrono::nanoseconds mReadyDuration = 10ms;
|
|
static constexpr int mIterations = 100;
|
|
const scheduler::VSyncDispatch::CallbackToken mFakeToken{2398};
|
|
const std::string mName = "DispSyncSourceTest";
|
|
};
|
|
|
|
DispSyncSourceTest::DispSyncSourceTest() {
|
|
const ::testing::TestInfo* const test_info =
|
|
::testing::UnitTest::GetInstance()->current_test_info();
|
|
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
|
|
}
|
|
|
|
DispSyncSourceTest::~DispSyncSourceTest() {
|
|
const ::testing::TestInfo* const test_info =
|
|
::testing::UnitTest::GetInstance()->current_test_info();
|
|
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
|
|
}
|
|
|
|
void DispSyncSourceTest::onVSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
|
|
nsecs_t deadlineTimestamp) {
|
|
ALOGD("onVSyncEvent: %" PRId64, when);
|
|
|
|
mVSyncEventCallRecorder.recordCall(when, expectedVSyncTimestamp, deadlineTimestamp);
|
|
}
|
|
|
|
void DispSyncSourceTest::createDispSync() {
|
|
mVSyncDispatch = std::make_unique<MockVSyncDispatch>();
|
|
}
|
|
|
|
void DispSyncSourceTest::createDispSyncSource() {
|
|
mDispSyncSource =
|
|
std::make_unique<scheduler::DispSyncSource>(*mVSyncDispatch, mWorkDuration,
|
|
mReadyDuration, true, mName.c_str());
|
|
mDispSyncSource->setCallback(this);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------
|
|
* Test cases
|
|
*/
|
|
|
|
TEST_F(DispSyncSourceTest, createDispSync) {
|
|
createDispSync();
|
|
EXPECT_TRUE(mVSyncDispatch);
|
|
}
|
|
|
|
TEST_F(DispSyncSourceTest, createDispSyncSource) {
|
|
createDispSync();
|
|
|
|
InSequence seq;
|
|
EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).WillOnce(Return(mFakeToken));
|
|
EXPECT_CALL(*mVSyncDispatch, cancel(mFakeToken))
|
|
.WillOnce(Return(scheduler::CancelResult::Cancelled));
|
|
EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mFakeToken)).WillOnce(Return());
|
|
createDispSyncSource();
|
|
|
|
EXPECT_TRUE(mDispSyncSource);
|
|
}
|
|
|
|
TEST_F(DispSyncSourceTest, noCallbackAfterInit) {
|
|
createDispSync();
|
|
|
|
InSequence seq;
|
|
EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
|
|
createDispSyncSource();
|
|
|
|
EXPECT_TRUE(mDispSyncSource);
|
|
|
|
// DispSyncSource starts with Vsync disabled
|
|
mVSyncDispatch->triggerCallbacks();
|
|
EXPECT_FALSE(mVSyncEventCallRecorder.waitForUnexpectedCall().has_value());
|
|
}
|
|
|
|
TEST_F(DispSyncSourceTest, waitForCallbacks) {
|
|
createDispSync();
|
|
|
|
InSequence seq;
|
|
EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch,
|
|
schedule(_, Truly([&](auto timings) {
|
|
return timings.workDuration == mWorkDuration.count() &&
|
|
timings.readyDuration == mReadyDuration.count();
|
|
})))
|
|
.Times(mIterations + 1);
|
|
EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
|
|
createDispSyncSource();
|
|
|
|
EXPECT_TRUE(mDispSyncSource);
|
|
|
|
mDispSyncSource->setVSyncEnabled(true);
|
|
for (int i = 0; i < mIterations; i++) {
|
|
mVSyncDispatch->triggerCallbacks();
|
|
const auto callbackData = mVSyncEventCallRecorder.waitForCall();
|
|
ASSERT_TRUE(callbackData.has_value());
|
|
const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
|
|
EXPECT_EQ(when, expectedVSyncTimestamp - mWorkDuration.count() - mReadyDuration.count());
|
|
}
|
|
}
|
|
|
|
TEST_F(DispSyncSourceTest, waitForCallbacksWithDurationChange) {
|
|
createDispSync();
|
|
|
|
InSequence seq;
|
|
EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch,
|
|
schedule(_, Truly([&](auto timings) {
|
|
return timings.workDuration == mWorkDuration.count() &&
|
|
timings.readyDuration == mReadyDuration.count();
|
|
})))
|
|
.Times(1);
|
|
|
|
createDispSyncSource();
|
|
|
|
EXPECT_TRUE(mDispSyncSource);
|
|
|
|
mDispSyncSource->setVSyncEnabled(true);
|
|
EXPECT_CALL(*mVSyncDispatch,
|
|
schedule(_, Truly([&](auto timings) {
|
|
return timings.workDuration == mWorkDuration.count() &&
|
|
timings.readyDuration == mReadyDuration.count();
|
|
})))
|
|
.Times(mIterations);
|
|
for (int i = 0; i < mIterations; i++) {
|
|
mVSyncDispatch->triggerCallbacks();
|
|
const auto callbackData = mVSyncEventCallRecorder.waitForCall();
|
|
ASSERT_TRUE(callbackData.has_value());
|
|
const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
|
|
EXPECT_EQ(when, expectedVSyncTimestamp - mWorkDuration.count() - mReadyDuration.count());
|
|
}
|
|
|
|
const auto newDuration = mWorkDuration / 2;
|
|
EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
|
|
return timings.workDuration == newDuration.count() &&
|
|
timings.readyDuration == 0;
|
|
})))
|
|
.Times(1);
|
|
mDispSyncSource->setDuration(newDuration, 0ns);
|
|
|
|
EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
|
|
return timings.workDuration == newDuration.count() &&
|
|
timings.readyDuration == 0;
|
|
})))
|
|
.Times(mIterations);
|
|
for (int i = 0; i < mIterations; i++) {
|
|
mVSyncDispatch->triggerCallbacks();
|
|
const auto callbackData = mVSyncEventCallRecorder.waitForCall();
|
|
ASSERT_TRUE(callbackData.has_value());
|
|
const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
|
|
EXPECT_EQ(when, expectedVSyncTimestamp - newDuration.count());
|
|
}
|
|
|
|
EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
|
|
EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace android
|