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 #include <android/performance_hint.h>
18 #include <assert.h>
19 #include <jni.h>
20
21 #include <algorithm>
22 #include <chrono>
23 #include <cstdlib>
24 #include <functional>
25 #include <map>
26 #include <random>
27 #include <set>
28 #include <sstream>
29 #include <string>
30 #include <thread>
31 #include <vector>
32
33 #include "AndroidOut.h"
34 #include "JNIManager.h"
35 #include "Renderer.h"
36 #include "Utility.h"
37
38 using namespace std::chrono_literals;
39
40 const constexpr auto kDrawingTimeout = 3s;
41 const constexpr int kSamples = 800;
42 const constexpr int kCalibrationSamples = 800;
43
getRenderer(android_app * pApp)44 Renderer *getRenderer(android_app *pApp) {
45 return (pApp->userData) ? reinterpret_cast<Renderer *>(pApp->userData) : nullptr;
46 }
47
48 // Converts lists of numbers into strings, so they can be
49 // passed up to the Java code the results map.
50 template <typename T>
serializeValues(const std::vector<T> & values)51 std::string serializeValues(const std::vector<T> &values) {
52 std::stringstream stream;
53 for (auto &&value : values) {
54 stream << value;
55 stream << ",";
56 }
57 std::string out = stream.str();
58 out.pop_back(); // remove the last comma
59 return out;
60 }
61
62 // Generalizes the loop used to draw frames so that it can be easily started and stopped
63 // back to back with different parameters, or after adjustments such as target time adjustments.
drawFrames(int count,android_app * pApp,int & events,android_poll_source * & pSource,std::string testName="")64 FrameStats drawFrames(int count, android_app *pApp, int &events, android_poll_source *&pSource,
65 std::string testName = "") {
66 bool namedTest = testName.size() > 0;
67 std::vector<int64_t> durations{};
68 std::vector<int64_t> intervals{};
69
70 auto drawStart = std::chrono::steady_clock::now();
71 // Iter is -1 so we have a buffer frame before it starts, to eat any delay from time spent
72 // between tests
73 for (int iter = -1; iter < count && !pApp->destroyRequested;) {
74 if (std::chrono::steady_clock::now() - drawStart > kDrawingTimeout) {
75 aout << "Stops drawing on " << kDrawingTimeout.count() << "s timeout for test "
76 << (namedTest ? testName : "unnamed") << std::endl;
77 break;
78 }
79 int retval = ALooper_pollOnce(0, nullptr, &events, (void **)&pSource);
80 while (retval == ALOOPER_POLL_CALLBACK) {
81 retval = ALooper_pollOnce(0, nullptr, &events, (void **)&pSource);
82 }
83 if (retval >= 0 && pSource) {
84 pSource->process(pApp, pSource);
85 }
86 if (pApp->userData) {
87 // Don't add metrics for buffer frames
88 if (iter > -1) {
89 thread_local auto lastStart = std::chrono::steady_clock::now();
90 auto start = std::chrono::steady_clock::now();
91
92 // Render a frame
93 jlong spinTime = getRenderer(pApp)->render();
94 getRenderer(pApp)->reportActualWorkDuration(spinTime);
95 durations.push_back(spinTime);
96 intervals.push_back((start - lastStart).count());
97 lastStart = start;
98 }
99 ++iter;
100 }
101 }
102
103 if (namedTest) {
104 getRenderer(pApp)->addResult(testName + "_durations", serializeValues(durations));
105 getRenderer(pApp)->addResult(testName + "_intervals", serializeValues(intervals));
106 }
107
108 return getRenderer(pApp)->getFrameStats(durations, intervals, testName);
109 }
110
drawFramesWithTarget(int64_t targetDuration,int & events,android_app * pApp,android_poll_source * & pSource,std::string testName="")111 FrameStats drawFramesWithTarget(int64_t targetDuration, int &events, android_app *pApp,
112 android_poll_source *&pSource, std::string testName = "") {
113 getRenderer(pApp)->updateTargetWorkDuration(targetDuration);
114 return drawFrames(kSamples, pApp, events, pSource, testName);
115 }
116
117 // Finds the test settings that best match this device, and returns the
118 // duration of the frame's work
calibrate(int & events,android_app * pApp,android_poll_source * & pSource)119 double calibrate(int &events, android_app *pApp, android_poll_source *&pSource) {
120 FrameStats calibration[2];
121 getRenderer(pApp)->setNumHeads(1);
122
123 // Find a number of heads that gives a work duration approximately equal
124 // to 1/4 the vsync period. This gives enough time for the frame to finish
125 // everything, while still providing enough overhead that differences are easy
126 // to notice.
127 calibration[0] = drawFrames(kCalibrationSamples, pApp, events, pSource);
128 getRenderer(pApp)->setNumHeads(200);
129 calibration[1] = drawFrames(kCalibrationSamples, pApp, events, pSource);
130
131 double target = calibration[1].medianFrameInterval / 6.0;
132 aout << "Goal duration: " << (int)target << std::endl;
133 double perHeadDuration =
134 (calibration[1].medianWorkDuration - calibration[0].medianWorkDuration) / 200.0;
135 aout << "per-head duration: " << (int)perHeadDuration << std::endl;
136 int heads = (target - static_cast<double>(calibration[0].medianWorkDuration)) / perHeadDuration;
137
138 getRenderer(pApp)->addResult("goal_duration", std::to_string(static_cast<int>(target)));
139 getRenderer(pApp)->addResult("heads_count", std::to_string(heads));
140
141 getRenderer(pApp)->setNumHeads(std::max(heads, 1));
142 return target;
143 }
144
145 // /*!
146 // * Handles commands sent to this Android application
147 // * @param pApp the app the commands are coming from
148 // * @param cmd the command to handle
149 // */
handle_cmd(android_app * pApp,int32_t cmd)150 void handle_cmd(android_app *pApp, int32_t cmd) {
151 switch (cmd) {
152 case APP_CMD_INIT_WINDOW:
153 pApp->userData = new Renderer(pApp);
154 break;
155 case APP_CMD_TERM_WINDOW:
156 // The window is being destroyed. Use this to clean up your userData to avoid leaking
157 // resources.
158 //
159 // We have to check if userData is assigned just in case this comes in really quickly
160 if (pApp->userData) {
161 auto *pRenderer = getRenderer(pApp);
162 Utility::setFailure("App was closed while running!", pRenderer);
163 }
164 break;
165 default:
166 break;
167 }
168 }
169
android_main(struct android_app * pApp)170 void android_main(struct android_app *pApp) {
171 app_dummy();
172
173 // Register an event handler for Android events
174 pApp->onAppCmd = handle_cmd;
175
176 JNIManager &manager = JNIManager::getInstance();
177 manager.setApp(pApp);
178
179 int events;
180 android_poll_source *pSource = nullptr;
181
182 // Ensure renderer is initialized
183 drawFrames(1, pApp, events, pSource);
184
185 bool supported = getRenderer(pApp)->getAdpfSupported();
186
187 if (!supported) {
188 JNIManager::sendResultsToJava(getRenderer(pApp)->getResults());
189 return;
190 }
191
192 std::this_thread::sleep_for(10s);
193 getRenderer(pApp)->setNumHeads(100);
194 // Run an initial load to get the CPU active and stable
195 drawFrames(kCalibrationSamples, pApp, events, pSource);
196
197 FrameStats initialStats = drawFrames(kSamples, pApp, events, pSource);
198
199 std::vector<pid_t> tids;
200 tids.push_back(gettid());
201 getRenderer(pApp)->startHintSession(tids, 6 * initialStats.medianWorkDuration);
202 if (!getRenderer(pApp)->isHintSessionRunning()) {
203 Utility::setFailure("Session failed to start!", getRenderer(pApp));
204 }
205
206 // Do an initial load with the session to let CPU settle
207 drawFrames(kCalibrationSamples / 2, pApp, events, pSource);
208
209 double calibratedTarget = calibrate(events, pApp, pSource);
210
211 auto testNames = JNIManager::getInstance().getTestNames();
212 std::set<std::string> testSet{testNames.begin(), testNames.end()};
213 std::vector<std::function<void()>> tests;
214
215 FrameStats baselineStats = drawFrames(kSamples, pApp, events, pSource, "baseline");
216
217 double calibrationAccuracy = 1.0 -
218 (abs(static_cast<double>(baselineStats.medianWorkDuration) - calibratedTarget) /
219 calibratedTarget);
220 getRenderer(pApp)->addResult("calibration_accuracy", std::to_string(calibrationAccuracy));
221
222 const int64_t lightTarget = 6 * baselineStats.medianWorkDuration;
223
224 // Used to figure out efficiency score on actual runs
225 getRenderer(pApp)->setBaselineMedian(baselineStats.medianWorkDuration);
226
227 // Set heavy target to be slightly smaller than the baseline to ensure a boost is necessary
228 const int64_t heavyTarget = (3 * baselineStats.medianWorkDuration) / 4;
229
230 if (testSet.count("heavy_load") > 0) {
231 tests.push_back(
232 [&]() { drawFramesWithTarget(heavyTarget, events, pApp, pSource, "heavy_load"); });
233 }
234
235 if (testSet.count("light_load") > 0) {
236 tests.push_back(
237 [&]() { drawFramesWithTarget(lightTarget, events, pApp, pSource, "light_load"); });
238 }
239
240 if (testSet.count("transition_load") > 0) {
241 tests.push_back([&]() {
242 drawFramesWithTarget(lightTarget, events, pApp, pSource, "transition_load_1");
243 drawFramesWithTarget(heavyTarget, events, pApp, pSource, "transition_load_2");
244 drawFramesWithTarget(lightTarget, events, pApp, pSource, "transition_load_3");
245 });
246 }
247
248 std::shuffle(tests.begin(), tests.end(), std::default_random_engine{});
249
250 for (auto &test : tests) {
251 test();
252 }
253
254 JNIManager::sendResultsToJava(getRenderer(pApp)->getResults());
255 }
256