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