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 #pragma once 17 18 #include <EGL/egl.h> 19 #include <android/native_activity.h> 20 #include <android/native_window.h> 21 #include <android/performance_hint.h> 22 #include <android/surface_control.h> 23 #include <jni.h> 24 25 #include <chrono> 26 #include <map> 27 #include <memory> 28 #include <optional> 29 30 #include "EventLoop.h" 31 #include "Model.h" 32 #include "Shader.h" 33 #include "external/android_native_app_glue.h" 34 35 const constexpr size_t kStages = 3; 36 const constexpr size_t kHeadsPerStage = 4; 37 const constexpr size_t kHeads = kStages * kHeadsPerStage; 38 39 struct android_app; 40 41 struct Vsync { 42 size_t index; 43 int64_t deadlineNanos; 44 int64_t presentationNanos; 45 int64_t vsyncId; 46 }; 47 48 struct VsyncData { 49 int64_t frameTimeNanos; 50 size_t numVsyncs; 51 size_t preferredVsync; 52 std::vector<Vsync> vsyncs{}; 53 }; 54 55 /*! 56 * The output of running the per-stage lambda 57 */ 58 struct StageState { 59 int64_t start; 60 int64_t end; 61 std::array<Model, kHeadsPerStage> models{}; 62 }; 63 64 /*! 65 * The output state of running the whole stack of the per-stage updates for each stage 66 */ 67 struct StackState { 68 Vsync intendedVsync; 69 std::array<StageState, kStages> stages{}; 70 }; 71 72 Vsync &getClosestCallbackToTarget(int64_t target, VsyncData &data); 73 74 struct FrameStats { 75 /*! 76 * Median of the durations 77 */ 78 int64_t medianWorkDuration; 79 /*! 80 * Median of the intervals 81 */ 82 int64_t medianFrameInterval; 83 /*! 84 * Standard deviation of a given run 85 */ 86 double deviation; 87 /*! 88 * The total number of frames that exceeded target 89 */ 90 std::optional<int64_t> exceededCount; 91 /*! 92 * The percent of frames that exceeded target 93 */ 94 std::optional<double> exceededFraction; 95 /*! 96 * Efficiency of a given run is calculated by how close to min(target, baseline) the median is 97 */ 98 std::optional<double> efficiency; 99 /*! 100 * Median of the target duration that was closest to what we wanted 101 */ 102 int64_t medianClosestDeadlineToTarget; 103 // Actual target duration, related to session target but not exactly the same 104 // This is based on the intended frame deadline based on results from Choreographer 105 int64_t actualTargetDuration; dumpFrameStats106 void dump(std::string testName) const { 107 aerr << "Stats for: " << testName; 108 aerr << "medianWorkDuration: " << medianWorkDuration << std::endl; 109 aerr << "medianFrameInterval: " << medianFrameInterval << std::endl; 110 aerr << "deviation: " << deviation << std::endl; 111 aerr << "exceededCount: " << exceededCount.value_or(-1) << std::endl; 112 aerr << "exceededFraction: " << exceededFraction.value_or(-1) << std::endl; 113 aerr << "efficiency: " << efficiency.value_or(-1) << std::endl; 114 aerr << "medianClosestDeadlineToTarget: " << medianClosestDeadlineToTarget << std::endl; 115 aerr << "actualTargetDuration: " << actualTargetDuration << std::endl; 116 }; 117 }; 118 119 struct FrameBatchData { 120 std::vector<int64_t> durations{}; 121 std::vector<int64_t> intervals{}; 122 std::vector<VsyncData> vsyncs{}; 123 std::vector<Vsync> selectedVsync{}; 124 }; 125 126 Renderer *getRenderer(); 127 128 class Renderer { 129 public: 130 /*! 131 * @param pApp the android_app this Renderer belongs to, needed to configure GL 132 */ Renderer(android_app * pApp)133 inline Renderer(android_app *pApp) 134 : app_(pApp), 135 display_(EGL_NO_DISPLAY), 136 surface_(EGL_NO_SURFACE), 137 context_(EGL_NO_CONTEXT), 138 width_(0), 139 height_(0), 140 shaderNeedsNewProjectionMatrix_(true) { 141 threadedInit(); 142 } 143 144 virtual ~Renderer(); 145 146 /*! 147 * Draw all models waiting to render in a given StackState. 148 */ 149 void render(const StackState &state); 150 151 /*! 152 * Attempts to start hint session and returns whether ADPF is supported on a given device. 153 */ 154 bool startHintSession(int64_t target); 155 void closeHintSession(); 156 void reportActualWorkDuration(int64_t duration); 157 void updateTargetWorkDuration(int64_t target); 158 bool isHintSessionRunning(); 159 int64_t getTargetWorkDuration(); 160 161 /*! 162 * Sets the number of iterations of physics before during each draw to control the CPU overhead. 163 */ 164 void setPhysicsIterations(int iterations); 165 166 /*! 167 * Adds an entry to the final result map that gets passed up to the Java side of the app, and 168 * eventually to the test runner. 169 */ 170 void addResult(std::string name, std::string value); 171 172 /*! 173 * Retrieve the results map. 174 */ 175 std::map<std::string, std::string> &getResults(); 176 177 /*! 178 * Sets the baseline median, used to determine efficiency score 179 */ 180 void setBaselineMedian(int64_t median); 181 182 /*! 183 * Calculates the above frame stats for a given run 184 */ 185 FrameStats getFrameStats(FrameBatchData &batchData, std::string &testName); 186 187 void setChoreographer(AChoreographer *choreographer); 188 FrameStats drawFramesSync(int frames, int &events, android_poll_source *&pSource, 189 std::string testName = ""); 190 191 /*! 192 * Run a few frames to figure out what vsync targets SF uses 193 */ 194 std::vector<int64_t> findVsyncTargets(int &events, android_poll_source *&pSource, 195 int numFrames); 196 197 static Renderer *getInstance(); 198 static void makeInstance(android_app *pApp); 199 200 private: 201 /*! 202 * Creates the set of android heads for load testing 203 */ 204 void createHeads(); 205 206 /*! 207 * Swap the waiting render buffer after everything is drawn 208 */ 209 void swapBuffers(const StackState &state); 210 211 /*! 212 * Performs necessary OpenGL initialization. Customize this if you want to change your EGL 213 * context or application-wide settings. 214 */ 215 void initRenderer(std::promise<bool> &syncOnCompletion); 216 217 /*! 218 * Run initRenderer on the drawing thread to hold the context there 219 */ 220 void threadedInit(); 221 222 /*! 223 * @brief we have to check every frame to see if the framebuffer has changed in size. If it has, 224 * update the viewport accordingly 225 */ 226 void updateRenderArea(); 227 228 /*! 229 * Run physics and spin the models in a pipeline 230 */ 231 StageState updateModels(StageState &lastUpdate); 232 233 void choreographerCallback(const AChoreographerFrameCallbackData *data); 234 235 /*! 236 * Passed directly choreographer as the callback method 237 */ 238 static void rawChoreographerCallback(const AChoreographerFrameCallbackData *data, void *appPtr); 239 240 /*! 241 * Post a new callback to Choreographer for the next frame 242 */ 243 void postCallback(); 244 245 android_app *app_; 246 EGLDisplay display_; 247 EGLSurface surface_; 248 EGLContext context_; 249 EGLint width_; 250 EGLint height_; 251 APerformanceHintSession *hintSession_ = nullptr; 252 APerformanceHintManager *hintManager_ = nullptr; 253 AChoreographer *choreographer_; 254 int64_t lastTarget_ = 0; 255 int64_t baselineMedian_ = 0; 256 257 bool shaderNeedsNewProjectionMatrix_ = true; 258 259 /*! 260 * Schedules a method to run against each level of the pipeline. At each step, the 261 * method will be passed a "Data" object and output an object of the same type, allowing 262 * different levels of the same pipeline to easily share data or work on the same problem. 263 */ 264 template <class Data> recursiveSchedule(std::function<void (int level,Data & data)> fn,int level,Data && data)265 void recursiveSchedule(std::function<void(int level, Data &data)> fn, int level, Data &&data) { 266 loops_[level].queueWork( 267 [=, this, fn = std::move(fn), dataInner = std::move(data)]() mutable { 268 // Run the method 269 fn(level, dataInner); 270 271 if (level > 0) { 272 this->recursiveSchedule(fn, level - 1, std::move(dataInner)); 273 } 274 }); 275 } 276 277 template <class Data> stackSchedule(std::function<void (int level,Data & data)> fn,Data startData)278 void stackSchedule(std::function<void(int level, Data &data)> fn, Data startData) { 279 recursiveSchedule<Data>(fn, loops_.size() - 1, std::move(startData)); 280 } 281 282 std::unique_ptr<Shader> shader_; 283 std::array<StageState, kStages> latest_stage_models_; 284 int headsSize_ = 0; 285 int physicsIterations_ = 1; 286 287 /*! 288 * Holds onto the results object in the renderer, so 289 * we can reach the data anywhere in the rendering step. 290 */ 291 std::map<std::string, std::string> results_; 292 293 std::array<EventLoop, kStages> loops_{}; 294 EventLoop drawLoop_{}; 295 std::vector<pid_t> tids_{}; 296 std::vector<int64_t> targets_{}; 297 298 FrameBatchData batchData_{}; 299 300 std::optional<std::function<void(VsyncData &data)>> wakeupMethod_ = std::nullopt; 301 302 std::optional<int64_t> lastStart_ = std::nullopt; 303 int framesRemaining_ = 0; 304 std::promise<bool> framesDone_{}; 305 306 static std::unique_ptr<Renderer> sRenderer; 307 }; 308