1 /*
2  * Copyright 2019 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 #include <thread>
18 
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 
22 #include <scheduler/Timer.h>
23 
24 #include "Scheduler/VSyncDispatchTimerQueue.h"
25 #include "Scheduler/VSyncTracker.h"
26 
27 using namespace testing;
28 using namespace std::literals;
29 
30 namespace android::scheduler {
31 
32 template <typename Rep, typename Per>
toNs(std::chrono::duration<Rep,Per> const & tp)33 constexpr nsecs_t toNs(std::chrono::duration<Rep, Per> const& tp) {
34     return std::chrono::duration_cast<std::chrono::nanoseconds>(tp).count();
35 }
36 
37 class FixedRateIdealStubTracker : public VSyncTracker {
38 public:
FixedRateIdealStubTracker()39     FixedRateIdealStubTracker() : mPeriod{toNs(3ms)} {}
40 
addVsyncTimestamp(nsecs_t)41     bool addVsyncTimestamp(nsecs_t) final { return true; }
42 
nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const43     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
44         auto const floor = timePoint % mPeriod;
45         if (floor == 0) {
46             return timePoint;
47         }
48         return timePoint - floor + mPeriod;
49     }
50 
currentPeriod() const51     nsecs_t currentPeriod() const final { return mPeriod; }
52 
setPeriod(nsecs_t)53     void setPeriod(nsecs_t) final {}
resetModel()54     void resetModel() final {}
needsMoreSamples() const55     bool needsMoreSamples() const final { return false; }
isVSyncInPhase(nsecs_t,Fps) const56     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
setRenderRate(Fps)57     void setRenderRate(Fps) final {}
dump(std::string &) const58     void dump(std::string&) const final {}
59 
60 private:
61     nsecs_t const mPeriod;
62 };
63 
64 class VRRStubTracker : public VSyncTracker {
65 public:
VRRStubTracker(nsecs_t period)66     VRRStubTracker(nsecs_t period) : mPeriod{period} {}
67 
addVsyncTimestamp(nsecs_t)68     bool addVsyncTimestamp(nsecs_t) final { return true; }
69 
nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const70     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
71         std::lock_guard lock(mMutex);
72         auto const normalized_to_base = time_point - mBase;
73         auto const floor = (normalized_to_base) % mPeriod;
74         if (floor == 0) {
75             return time_point;
76         }
77         return normalized_to_base - floor + mPeriod + mBase;
78     }
79 
set_interval(nsecs_t interval,nsecs_t last_known)80     void set_interval(nsecs_t interval, nsecs_t last_known) {
81         std::lock_guard lock(mMutex);
82         mPeriod = interval;
83         mBase = last_known;
84     }
85 
currentPeriod() const86     nsecs_t currentPeriod() const final {
87         std::lock_guard lock(mMutex);
88         return mPeriod;
89     }
90 
setPeriod(nsecs_t)91     void setPeriod(nsecs_t) final {}
resetModel()92     void resetModel() final {}
needsMoreSamples() const93     bool needsMoreSamples() const final { return false; }
isVSyncInPhase(nsecs_t,Fps) const94     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
setRenderRate(Fps)95     void setRenderRate(Fps) final {}
dump(std::string &) const96     void dump(std::string&) const final {}
97 
98 private:
99     std::mutex mutable mMutex;
100     nsecs_t mPeriod;
101     nsecs_t mBase = 0;
102 };
103 
104 struct VSyncDispatchRealtimeTest : testing::Test {
105     static nsecs_t constexpr mDispatchGroupThreshold = toNs(100us);
106     static nsecs_t constexpr mVsyncMoveThreshold = toNs(500us);
107     static size_t constexpr mIterations = 20;
108 };
109 
110 class RepeatingCallbackReceiver {
111 public:
RepeatingCallbackReceiver(std::shared_ptr<VSyncDispatch> dispatch,nsecs_t workload,nsecs_t readyDuration)112     RepeatingCallbackReceiver(std::shared_ptr<VSyncDispatch> dispatch, nsecs_t workload,
113                               nsecs_t readyDuration)
114           : mWorkload(workload),
115             mReadyDuration(readyDuration),
116             mCallback(
117                     dispatch, [&](auto time, auto, auto) { callback_called(time); }, "repeat0") {}
118 
repeatedly_schedule(size_t iterations,std::function<void (nsecs_t)> const & onEachFrame)119     void repeatedly_schedule(size_t iterations, std::function<void(nsecs_t)> const& onEachFrame) {
120         mCallbackTimes.reserve(iterations);
121         mCallback.schedule(
122                 {.workDuration = mWorkload,
123                  .readyDuration = mReadyDuration,
124                  .earliestVsync = systemTime(SYSTEM_TIME_MONOTONIC) + mWorkload + mReadyDuration});
125 
126         for (auto i = 0u; i < iterations - 1; i++) {
127             std::unique_lock lock(mMutex);
128             mCv.wait(lock, [&] { return mCalled; });
129             mCalled = false;
130             auto last = mLastTarget;
131             lock.unlock();
132 
133             onEachFrame(last);
134 
135             mCallback.schedule({.workDuration = mWorkload,
136                                 .readyDuration = mReadyDuration,
137                                 .earliestVsync = last + mWorkload + mReadyDuration});
138         }
139 
140         // wait for the last callback.
141         std::unique_lock lock(mMutex);
142         mCv.wait(lock, [&] { return mCalled; });
143     }
144 
with_callback_times(std::function<void (std::vector<nsecs_t> const &)> const & fn) const145     void with_callback_times(std::function<void(std::vector<nsecs_t> const&)> const& fn) const {
146         fn(mCallbackTimes);
147     }
148 
149 private:
callback_called(nsecs_t time)150     void callback_called(nsecs_t time) {
151         std::lock_guard lock(mMutex);
152         mCallbackTimes.push_back(time);
153         mCalled = true;
154         mLastTarget = time;
155         mCv.notify_all();
156     }
157 
158     nsecs_t const mWorkload;
159     nsecs_t const mReadyDuration;
160     VSyncCallbackRegistration mCallback;
161 
162     std::mutex mMutex;
163     std::condition_variable mCv;
164     bool mCalled = false;
165     nsecs_t mLastTarget = 0;
166     std::vector<nsecs_t> mCallbackTimes;
167 };
168 
TEST_F(VSyncDispatchRealtimeTest,triple_alarm)169 TEST_F(VSyncDispatchRealtimeTest, triple_alarm) {
170     auto tracker = std::make_shared<FixedRateIdealStubTracker>();
171     auto dispatch =
172             std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
173                                                       mDispatchGroupThreshold, mVsyncMoveThreshold);
174 
175     static size_t constexpr num_clients = 3;
176     std::array<RepeatingCallbackReceiver, num_clients>
177             cb_receiver{RepeatingCallbackReceiver(dispatch, toNs(1500us), toNs(2500us)),
178                         RepeatingCallbackReceiver(dispatch, toNs(0h), toNs(0h)),
179                         RepeatingCallbackReceiver(dispatch, toNs(1ms), toNs(3ms))};
180 
181     auto const on_each_frame = [](nsecs_t) {};
182     std::array<std::thread, num_clients> threads{
183             std::thread([&] { cb_receiver[0].repeatedly_schedule(mIterations, on_each_frame); }),
184             std::thread([&] { cb_receiver[1].repeatedly_schedule(mIterations, on_each_frame); }),
185             std::thread([&] { cb_receiver[2].repeatedly_schedule(mIterations, on_each_frame); }),
186     };
187 
188     for (auto it = threads.rbegin(); it != threads.rend(); it++) {
189         it->join();
190     }
191 
192     for (auto const& cbs : cb_receiver) {
193         cbs.with_callback_times([](auto times) { EXPECT_THAT(times.size(), Eq(mIterations)); });
194     }
195 }
196 
197 // starts at 333hz, slides down to 43hz
TEST_F(VSyncDispatchRealtimeTest,vascillating_vrr)198 TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) {
199     auto next_vsync_interval = toNs(3ms);
200     auto tracker = std::make_shared<VRRStubTracker>(next_vsync_interval);
201     auto dispatch =
202             std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
203                                                       mDispatchGroupThreshold, mVsyncMoveThreshold);
204 
205     RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
206 
207     auto const on_each_frame = [&](nsecs_t last_known) {
208         tracker->set_interval(next_vsync_interval += toNs(1ms), last_known);
209     };
210 
211     std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
212     eventThread.join();
213 
214     cb_receiver.with_callback_times([](auto times) { EXPECT_THAT(times.size(), Eq(mIterations)); });
215 }
216 
217 // starts at 333hz, jumps to 200hz at frame 10
TEST_F(VSyncDispatchRealtimeTest,fixed_jump)218 TEST_F(VSyncDispatchRealtimeTest, fixed_jump) {
219     auto tracker = std::make_shared<VRRStubTracker>(toNs(3ms));
220     auto dispatch =
221             std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
222                                                       mDispatchGroupThreshold, mVsyncMoveThreshold);
223 
224     RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
225 
226     auto jump_frame_counter = 0u;
227     auto constexpr jump_frame_at = 10u;
228     auto const on_each_frame = [&](nsecs_t last_known) {
229         if (jump_frame_counter++ == jump_frame_at) {
230             tracker->set_interval(toNs(5ms), last_known);
231         }
232     };
233     std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
234     eventThread.join();
235 
236     cb_receiver.with_callback_times([](auto times) { EXPECT_THAT(times.size(), Eq(mIterations)); });
237 }
238 } // namespace android::scheduler
239