• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "Renderer.h"
17 
18 #include <android/imagedecoder.h>
19 
20 #include <memory>
21 #include <numeric>
22 #include <string>
23 #include <vector>
24 // #include <iostream>
25 
26 #include <android/hardware_buffer_jni.h>
27 
28 #include <chrono>
29 
30 #include "AndroidOut.h"
31 #include "JNIManager.h"
32 #include "Shader.h"
33 #include "TextureAsset.h"
34 #include "Utility.h"
35 #include "android/performance_hint.h"
36 
37 using namespace std::chrono_literals;
38 //! executes glGetString and outputs the result to logcat
39 #define PRINT_GL_STRING(s) \
40     { aout << #s ": " << glGetString(s) << std::endl; }
41 
42 /*!
43  * @brief if glGetString returns a space separated list of elements, prints each one on a new line
44  *
45  * This works by creating an istringstream of the input c-style string. Then that is used to create
46  * a vector -- each element of the vector is a new element in the input string. Finally a foreach
47  * loop consumes this and outputs it to logcat using @a aout
48  */
49 #define PRINT_GL_STRING_AS_LIST(s)                                                 \
50     {                                                                              \
51         std::istringstream extensionStream((const char *)glGetString(s));          \
52         std::vector<std::string>                                                   \
53                 extensionList(std::istream_iterator<std::string>{extensionStream}, \
54                               std::istream_iterator<std::string>());               \
55         aout << #s ":\n";                                                          \
56         for (auto &extension : extensionList) {                                    \
57             aout << extension << "\n";                                             \
58         }                                                                          \
59         aout << std::endl;                                                         \
60     }
61 
62 //! Color for cornflower blue. Can be sent directly to glClearColor
63 #define CORNFLOWER_BLUE 100 / 255.f, 149 / 255.f, 237 / 255.f, 1
64 
65 // Vertex shader, you'd typically load this from assets
66 static const char *vertex = R"vertex(#version 300 es
67 in vec3 inPosition;
68 in vec2 inUV;
69 
70 out vec2 fragUV;
71 
72 uniform mat4 uProjection;
73 
74 void main() {
75     fragUV = inUV;
76     gl_Position = uProjection * vec4(inPosition, 1.0);
77 }
78 )vertex";
79 
80 // Fragment shader, you'd typically load this from assets
81 static const char *fragment = R"fragment(#version 300 es
82 precision mediump float;
83 
84 in vec2 fragUV;
85 
86 uniform sampler2D uTexture;
87 
88 out vec4 outColor;
89 
90 void main() {
91     outColor = texture(uTexture, fragUV);
92 }
93 )fragment";
94 
95 /*!
96  * Half the height of the projection matrix. This gives you a renderable area of height 4 ranging
97  * from -2 to 2
98  */
99 static constexpr float kProjectionHalfHeight = 2.f;
100 
101 /*!
102  * The near plane distance for the projection matrix. Since this is an orthographic projection
103  * matrix, it's convenient to have negative values for sorting (and avoiding z-fighting at 0).
104  */
105 static constexpr float kProjectionNearPlane = -1.f;
106 
107 /*!
108  * The far plane distance for the projection matrix. Since this is an orthographic projection
109  * matrix, it's convenient to have the far plane equidistant from 0 as the near plane.
110  */
111 static constexpr float kProjectionFarPlane = 1.f;
112 
113 std::shared_ptr<TextureAsset> Model::texture = nullptr;
114 
~Renderer()115 Renderer::~Renderer() {
116     if (display_ != EGL_NO_DISPLAY) {
117         eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
118         if (context_ != EGL_NO_CONTEXT) {
119             eglDestroyContext(display_, context_);
120             context_ = EGL_NO_CONTEXT;
121         }
122         if (surface_ != EGL_NO_SURFACE) {
123             eglDestroySurface(display_, surface_);
124             surface_ = EGL_NO_SURFACE;
125         }
126         eglTerminate(display_);
127         display_ = EGL_NO_DISPLAY;
128     }
129 }
130 
getRenderer()131 Renderer *getRenderer() {
132     return Renderer::getInstance();
133 }
134 
updateModels(StageState & lastUpdate)135 StageState Renderer::updateModels(StageState &lastUpdate) {
136     StageState output{
137             .models = lastUpdate.models,
138             .start = Utility::now(),
139     };
140 
141     // Get desired seconds per rotation
142     double spr = duration_cast<std::chrono::nanoseconds>(2s).count();
143 
144     // Figure out what angle the models need to be at
145     double offset = (output.start - lastUpdate.start);
146 
147     // Calculate the required spin as a fraction of a circle
148     auto spin = offset / spr;
149 
150     for (Model &model : output.models) {
151         model.addRotation(M_PI * 2.0 * spin);
152     }
153 
154     for (int j = 0; j < physicsIterations_; ++j) {
155         Model::applyPhysics(0.1f, output.models.data(), output.models.size(),
156                             static_cast<float>(width_) * kProjectionHalfHeight * 2 / height_,
157                             kProjectionHalfHeight * 2);
158     }
159 
160     output.end = Utility::now();
161     return output;
162 }
163 
render(const StackState & stack)164 void Renderer::render(const StackState &stack) {
165     // Check to see if the surface has changed size. This is _necessary_ to do every frame when
166     // using immersive mode as you'll get no other notification that your renderable area has
167     // changed.
168 
169     updateRenderArea();
170     assert(display_ != nullptr);
171     assert(surface_ != nullptr);
172     assert(shader_ != nullptr);
173 
174     // When the renderable area changes, the fprojection matrix has to also be updated. This is true
175     // even if you change from the sample orthographic projection matrix as your aspect ratio has
176     // likely changed.
177     if (shaderNeedsNewProjectionMatrix_) {
178         // a placeholder projection matrix allocated on the stack. Column-major memory layout
179         float projectionMatrix[16] = {0};
180 
181         // build an orthographic projection matrix for 2d rendering
182         Utility::buildOrthographicMatrix(projectionMatrix, kProjectionHalfHeight,
183                                          float(width_) / height_, kProjectionNearPlane,
184                                          kProjectionFarPlane);
185 
186         // send the matrix to the shader
187         // Note: the shader must be active for this to work.
188         assert(projectionMatrix != nullptr);
189 
190         if (shader_ != nullptr) {
191             shader_->setProjectionMatrix(projectionMatrix);
192         }
193 
194         // make sure the matrix isn't generated every frame
195         shaderNeedsNewProjectionMatrix_ = false;
196     }
197 
198     // clear the color buffer
199     glClear(GL_COLOR_BUFFER_BIT);
200 
201     // Render all the models. There's no depth testing in this sample so they're accepted in the
202     // order provided. But the sample EGL setup requests a 24 bit depth buffer so you could
203     // configure it at the end of initRenderer
204     for (auto &&stage : stack.stages) {
205         for (const Model &model : stage.models) {
206             shader_->drawModel(model);
207         }
208     }
209 }
210 
swapBuffers(const StackState &)211 void Renderer::swapBuffers(const StackState &) {
212     auto swapResult = eglSwapBuffers(display_, surface_);
213 }
214 
threadedInit()215 void Renderer::threadedInit() {
216     std::promise<bool> syncOnCompletion;
217 
218     drawLoop_.queueWork([this, &syncOnCompletion]() { initRenderer(syncOnCompletion); });
219 
220     syncOnCompletion.get_future().get();
221 
222     for (int i = 0; i < loops_.size(); ++i) {
223         tids_.push_back(loops_[i].getlooptid());
224     }
225     tids_.push_back(drawLoop_.getlooptid());
226 }
227 
initRenderer(std::promise<bool> & syncOnCompletion)228 void Renderer::initRenderer(std::promise<bool> &syncOnCompletion) {
229     // Choose your render attributes
230     constexpr EGLint attribs[] = {EGL_RENDERABLE_TYPE,
231                                   EGL_OPENGL_ES3_BIT,
232                                   EGL_SURFACE_TYPE,
233                                   EGL_WINDOW_BIT,
234                                   EGL_BLUE_SIZE,
235                                   8,
236                                   EGL_GREEN_SIZE,
237                                   8,
238                                   EGL_RED_SIZE,
239                                   8,
240                                   EGL_DEPTH_SIZE,
241                                   24,
242                                   EGL_NONE};
243 
244     // The default display is probably what you want on Android
245     auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
246     eglInitialize(display, nullptr, nullptr);
247 
248     // figure out how many configs there are
249     EGLint numConfigs;
250     eglChooseConfig(display, attribs, nullptr, 0, &numConfigs);
251 
252     // get the list of configurations
253     std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
254     eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
255 
256     // Find a config we like.
257     // Could likely just grab the first if we don't care about anything else in the config.
258     // Otherwise hook in your own heuristic
259     auto config =
260             *std::find_if(supportedConfigs.get(), supportedConfigs.get() + numConfigs,
261                           [&display](const EGLConfig &config) {
262                               EGLint red, green, blue, depth;
263                               if (eglGetConfigAttrib(display, config, EGL_RED_SIZE, &red) &&
264                                   eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &green) &&
265                                   eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &blue) &&
266                                   eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &depth)) {
267                                   aout << "Found config with " << red << ", " << green << ", "
268                                        << blue << ", " << depth << std::endl;
269                                   return red == 8 && green == 8 && blue == 8 && depth == 24;
270                               }
271                               return false;
272                           });
273 
274     // create the proper window surface
275     EGLint format;
276     eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
277     EGLSurface surface = eglCreateWindowSurface(display, config, app_->window, nullptr);
278 
279     // Create a GLES 3 context
280     EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
281     EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);
282 
283     // get some window metrics
284     auto madeCurrent = eglMakeCurrent(display, surface, surface, context);
285     assert(madeCurrent);
286 
287     display_ = display;
288     surface_ = surface;
289     context_ = context;
290 
291     width_ = static_cast<uint32_t>(ANativeWindow_getWidth(app_->window));
292     height_ = static_cast<uint32_t>(ANativeWindow_getHeight(app_->window));
293 
294     PRINT_GL_STRING(GL_VENDOR);
295     PRINT_GL_STRING(GL_RENDERER);
296     PRINT_GL_STRING(GL_VERSION);
297     PRINT_GL_STRING_AS_LIST(GL_EXTENSIONS);
298 
299     shader_ = std::unique_ptr<Shader>(
300             Shader::loadShader(vertex, fragment, "inPosition", "inUV", "uProjection"));
301     assert(shader_);
302 
303     // Note: there's only one shader in this demo, so I'll activate it here. For a more complex game
304     // you'll want to track the active shader and activate/deactivate it as necessary
305     shader_->activate();
306 
307     // setup any other gl related global states
308     glClearColor(CORNFLOWER_BLUE);
309 
310     // enable alpha globally for now, you probably don't want to do this in a game
311     glEnable(GL_BLEND);
312     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
313 
314     createHeads();
315     syncOnCompletion.set_value(true);
316 }
317 
updateRenderArea()318 void Renderer::updateRenderArea() {
319     EGLint width = static_cast<uint32_t>(ANativeWindow_getWidth(app_->window));
320     EGLint height = static_cast<uint32_t>(ANativeWindow_getHeight(app_->window));
321 
322     if (width != width_ || height != height_) {
323         width_ = width;
324         height_ = height;
325         glViewport(0, 0, width, height);
326 
327         // make sure that we lazily recreate the projection matrix before we render
328         shaderNeedsNewProjectionMatrix_ = true;
329     }
330 }
331 
findVsyncTargets(int & events,android_poll_source * & pSource,int numFrames)332 std::vector<int64_t> Renderer::findVsyncTargets(int &events, android_poll_source *&pSource,
333                                                 int numFrames) {
334     targets_ = {};
335     drawFramesSync(numFrames, events, pSource, "findVsyncTargets");
336     return targets_;
337 }
338 
createHeads()339 void Renderer::createHeads() {
340     auto assetManager = app_->activity->assetManager;
341     Model::texture = TextureAsset::loadAsset(assetManager, "android.png");
342     std::vector<Vertex> vertices = {
343             Vertex(Vector3{{-0.3, 0.3, 0}}, Vector2{{0, 0}}), // 0
344             Vertex(Vector3{{0.3, 0.3, 0}}, Vector2{{1, 0}}),  // 1
345             Vertex(Vector3{{0.3, -0.3, 0}}, Vector2{{1, 1}}), // 2
346             Vertex(Vector3{{-0.3, -0.3, 0}}, Vector2{{0, 1}}) // 3
347     };
348     std::vector<Index> indices = {0, 1, 2, 0, 2, 3};
349     Model baseModel{vertices, indices};
350     int id = 0;
351     for (auto &&stage : latest_stage_models_) {
352         for (int i = 0; i < stage.models.size(); ++i) {
353             ++id;
354             float angle = 2 * M_PI * (static_cast<float>(rand()) / static_cast<float>(RAND_MAX));
355             float x = 1.5 * static_cast<float>(rand()) / static_cast<float>(RAND_MAX) - 0.75;
356             float y = 3.0 * static_cast<float>(rand()) / static_cast<float>(RAND_MAX) - 1.5;
357             float mass = rand() % 10 + 1;
358             Vector3 offset{{x, y, 0}};
359             Model toAdd{baseModel};
360             toAdd.move(offset);
361             toAdd.setRotationOffset(angle);
362             toAdd.setMass(mass);
363             toAdd.setId(id);
364             stage.models[i] = toAdd;
365         }
366     }
367 }
368 
setPhysicsIterations(int iterations)369 void Renderer::setPhysicsIterations(int iterations) {
370     physicsIterations_ = iterations;
371 }
372 
startHintSession(int64_t target)373 bool Renderer::startHintSession(int64_t target) {
374     if (hintManager_ == nullptr) {
375         hintManager_ = APerformanceHint_getManager();
376     }
377     long preferredRate = APerformanceHint_getPreferredUpdateRateNanos(hintManager_);
378     results_["preferredRate"] = std::to_string(preferredRate);
379     if (preferredRate > 0 && hintSession_ == nullptr && hintManager_ != nullptr) {
380         std::vector<int> toSend(tids_);
381         toSend.push_back(gettid());
382         lastTarget_ = target;
383         hintSession_ =
384                 APerformanceHint_createSession(hintManager_, toSend.data(), toSend.size(), target);
385     }
386     bool supported = preferredRate > 0 && hintSession_ != nullptr;
387     results_["isHintSessionSupported"] = supported ? "true" : "false";
388     return supported;
389 }
390 
reportActualWorkDuration(int64_t duration)391 void Renderer::reportActualWorkDuration(int64_t duration) {
392     if (isHintSessionRunning()) {
393         int ret = APerformanceHint_reportActualWorkDuration(hintSession_, duration);
394         if (ret < 0) {
395             Utility::setFailure("Failed to report actual work duration with code " +
396                                         std::to_string(ret),
397                                 this);
398         }
399     }
400 }
401 
updateTargetWorkDuration(int64_t target)402 void Renderer::updateTargetWorkDuration(int64_t target) {
403     lastTarget_ = target;
404     if (isHintSessionRunning()) {
405         int ret = APerformanceHint_updateTargetWorkDuration(hintSession_, target);
406         if (ret < 0) {
407             Utility::setFailure("Failed to update target duration with code " + std::to_string(ret),
408                                 this);
409         }
410     }
411 }
412 
getTargetWorkDuration()413 int64_t Renderer::getTargetWorkDuration() {
414     return lastTarget_;
415 }
416 
isHintSessionRunning()417 bool Renderer::isHintSessionRunning() {
418     return hintSession_ != nullptr;
419 }
420 
closeHintSession()421 void Renderer::closeHintSession() {
422     APerformanceHint_closeSession(hintSession_);
423 }
424 
addResult(std::string name,std::string value)425 void Renderer::addResult(std::string name, std::string value) {
426     results_[name] = value;
427 }
428 
getResults()429 std::map<std::string, std::string> &Renderer::getResults() {
430     return results_;
431 }
432 
setBaselineMedian(int64_t median)433 void Renderer::setBaselineMedian(int64_t median) {
434     baselineMedian_ = median;
435 }
436 
437 template <typename T>
getMedian(std::vector<T> values)438 T getMedian(std::vector<T> values) {
439     std::sort(values.begin(), values.end());
440     return values[values.size() / 2];
441 }
442 
setChoreographer(AChoreographer * choreographer)443 void Renderer::setChoreographer(AChoreographer *choreographer) {
444     choreographer_ = choreographer;
445 }
446 
dumpCallbacksData(VsyncData & data)447 void dumpCallbacksData(VsyncData &data) {
448     aerr << "Frame time: " << data.frameTimeNanos << std::endl;
449     aerr << "Num callbacks: " << data.numVsyncs << std::endl;
450     aerr << "Preferred callback: " << data.preferredVsync << std::endl;
451     for (auto &&callback : data.vsyncs) {
452         aerr << "Callback " << callback.index << " has deadline: " << callback.deadlineNanos
453              << " which is: " << callback.deadlineNanos - data.frameTimeNanos
454              << " away, vsyncId: " << callback.vsyncId << std::endl;
455     }
456     aerr << "Finished dump " << std::endl;
457 }
458 
getClosestCallbackToTarget(int64_t target,VsyncData & data)459 Vsync &getClosestCallbackToTarget(int64_t target, VsyncData &data) {
460     int min_i = 0;
461     int64_t min_diff = std::abs(data.vsyncs[0].deadlineNanos - data.frameTimeNanos - target);
462     for (int i = 1; i < data.vsyncs.size(); ++i) {
463         int64_t diff = std::abs(data.vsyncs[i].deadlineNanos - data.frameTimeNanos - target);
464         if (diff < min_diff) {
465             min_diff = diff;
466             min_i = i;
467         }
468     }
469     return data.vsyncs[min_i];
470 }
471 
fromFrameCallbackData(const AChoreographerFrameCallbackData * data)472 VsyncData fromFrameCallbackData(const AChoreographerFrameCallbackData *data) {
473     VsyncData out{.frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(data),
474                   .numVsyncs = AChoreographerFrameCallbackData_getFrameTimelinesLength(data),
475                   .preferredVsync =
476                           AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(data),
477                   .vsyncs{}};
478     for (size_t i = 0; i < out.numVsyncs; ++i) {
479         out.vsyncs.push_back(
480                 {.index = i,
481                  .deadlineNanos =
482                          AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(data, i),
483                  .presentationNanos =
484                          AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
485                                  data, i),
486                  .vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(data, i)});
487     }
488     return out;
489 }
490 
rawChoreographerCallback(const AChoreographerFrameCallbackData * data,void *)491 void Renderer::rawChoreographerCallback(const AChoreographerFrameCallbackData *data, void *) {
492     static std::mutex choreographerMutex{};
493     assert(appPtr != nullptr);
494     std::scoped_lock lock(choreographerMutex);
495     getRenderer()->choreographerCallback(data);
496 }
497 
choreographerCallback(const AChoreographerFrameCallbackData * data)498 void Renderer::choreographerCallback(const AChoreographerFrameCallbackData *data) {
499     if (framesRemaining_ > 0) {
500         --framesRemaining_;
501     }
502     VsyncData callbackData = fromFrameCallbackData(data);
503     batchData_.vsyncs.push_back(callbackData);
504     // Allow a passed-in method to check on the callback data
505     if (wakeupMethod_ != std::nullopt) {
506         (*wakeupMethod_)(callbackData);
507     }
508     if (framesRemaining_ == 0 && targets_.size() == 0) {
509         dumpCallbacksData(callbackData);
510         for (int i = 0; i < callbackData.vsyncs.size(); ++i) {
511             targets_.push_back(callbackData.vsyncs[i].deadlineNanos - callbackData.frameTimeNanos);
512         }
513     }
514 
515     int64_t start = Utility::now();
516     if (lastStart_.has_value()) {
517         batchData_.intervals.push_back(start - *lastStart_);
518     }
519     lastStart_ = start;
520 
521     StackState startState{
522             .intendedVsync = getClosestCallbackToTarget(lastTarget_, callbackData),
523             .stages{},
524     };
525 
526     batchData_.selectedVsync.push_back(startState.intendedVsync);
527 
528     if (framesRemaining_ > 0) {
529         postCallback();
530     }
531 
532     // Copy here so the lambdas can use that copy and not the global one
533     int remain = framesRemaining_;
534 
535     stackSchedule<StackState>(
536             [=, startTime = start, this](int level, StackState &state) {
537                 // Render against the most recent physics data for this pipeline
538                 state.stages[level] = updateModels(latest_stage_models_[level]);
539 
540                 // Save the output from this physics step for the next frame to use
541                 latest_stage_models_[level] = state.stages[level];
542 
543                 // If we're at the end of the stack, push a draw job to the draw loop
544                 if (level == 0) {
545                     drawLoop_.queueWork([=, this]() {
546                         if (remain == 0) {
547                             framesDone_.set_value(true);
548                         }
549 
550                         render(state);
551                         // Update final stage time after render finishes
552 
553                         int64_t duration = Utility::now() - startTime;
554                         batchData_.durations.push_back(duration);
555                         if (isHintSessionRunning()) {
556                             reportActualWorkDuration(duration);
557                         }
558 
559                         swapBuffers(state);
560                     });
561                 }
562             },
563             std::move(startState));
564 }
565 
drawFramesSync(int frames,int & events,android_poll_source * & pSource,std::string testName)566 FrameStats Renderer::drawFramesSync(int frames, int &events, android_poll_source *&pSource,
567                                     std::string testName) {
568     bool namedTest = testName.size() > 0;
569 
570     // Make sure this can only run once at a time because the callback is unique
571     static std::mutex mainThreadLock;
572     framesDone_ = std::promise<bool>();
573     auto frameFuture = framesDone_.get_future();
574 
575     std::scoped_lock lock(mainThreadLock);
576     batchData_ = {};
577     framesRemaining_ = frames;
578     postCallback();
579 
580     while (true) {
581         int retval = ALooper_pollOnce(30, nullptr, &events, (void **)&pSource);
582         if (retval > 0 && pSource) {
583             pSource->process(app_, pSource);
584         }
585         auto status = frameFuture.wait_for(0s);
586         if (status == std::future_status::ready) {
587             break;
588         }
589     }
590     if (namedTest) {
591         addResult(testName + "_durations", Utility::serializeValues(batchData_.durations));
592         addResult(testName + "_intervals", Utility::serializeValues(batchData_.intervals));
593     }
594 
595     return getFrameStats(batchData_, testName);
596 }
597 
postCallback()598 void Renderer::postCallback() {
599     AChoreographer_postVsyncCallback(choreographer_, rawChoreographerCallback, nullptr);
600 }
601 
getFrameStats(FrameBatchData & batchData,std::string & testName)602 FrameStats Renderer::getFrameStats(FrameBatchData &batchData, std::string &testName) {
603     FrameStats stats;
604     stats.actualTargetDuration = lastTarget_;
605 
606     std::vector<int64_t> targets;
607     for (int i = 0; i < batchData.vsyncs.size(); ++i) {
608         VsyncData &vsyncData = batchData.vsyncs[i];
609         Vsync &selectedVsync = batchData.selectedVsync[i];
610         targets.push_back(selectedVsync.deadlineNanos - vsyncData.frameTimeNanos);
611     }
612 
613     stats.medianClosestDeadlineToTarget = getMedian(targets);
614 
615     double sum = std::accumulate(batchData.durations.begin(), batchData.durations.end(),
616                                  static_cast<double>(0.0));
617     double mean = sum / static_cast<double>(batchData.durations.size());
618     int dropCount = 0;
619     double varianceSum = 0;
620     for (int64_t &duration : batchData.durations) {
621         if (isHintSessionRunning() && duration > lastTarget_) {
622             ++dropCount;
623         }
624         varianceSum += (duration - mean) * (duration - mean);
625     }
626     std::vector<int64_t> selectedDeadlineDurations;
627     for (int i = 0; i < batchData.vsyncs.size(); ++i) {
628         selectedDeadlineDurations.push_back(batchData.selectedVsync[i].deadlineNanos -
629                                             batchData.vsyncs[i].frameTimeNanos);
630     }
631     if (batchData.durations.size() > 0) {
632         stats.medianWorkDuration = getMedian(batchData.durations);
633     }
634     if (batchData.intervals.size() > 0) {
635         stats.medianFrameInterval = getMedian(batchData.intervals);
636     }
637     stats.deviation = std::sqrt(varianceSum / static_cast<double>(batchData.durations.size() - 1));
638     if (isHintSessionRunning()) {
639         stats.exceededCount = dropCount;
640         stats.exceededFraction =
641                 static_cast<double>(dropCount) / static_cast<double>(batchData.durations.size());
642         stats.efficiency = static_cast<double>(sum) /
643                 static_cast<double>(batchData.durations.size() *
644                                     std::min(lastTarget_, baselineMedian_));
645     }
646 
647     if (testName.size() > 0) {
648         addResult(testName + "_selected_deadline_durations",
649                   Utility::serializeValues(selectedDeadlineDurations));
650         addResult(testName + "_sum", std::to_string(sum));
651         addResult(testName + "_num_durations", std::to_string(batchData.durations.size()));
652         addResult(testName + "_mean", std::to_string(mean));
653         addResult(testName + "_median", std::to_string(stats.medianWorkDuration));
654         addResult(testName + "_median_interval", std::to_string(stats.medianFrameInterval));
655         addResult(testName + "_median_deadline_duration",
656                   std::to_string(stats.medianClosestDeadlineToTarget));
657         addResult(testName + "_intended_deadline_duration",
658                   std::to_string(stats.actualTargetDuration));
659         addResult(testName + "_deviation", std::to_string(stats.deviation));
660         if (isHintSessionRunning()) {
661             addResult(testName + "_target", std::to_string(getTargetWorkDuration()));
662             addResult(testName + "_target_exceeded_count", std::to_string(*stats.exceededCount));
663             addResult(testName + "_target_exceeded_fraction",
664                       std::to_string(*stats.exceededFraction));
665             addResult(testName + "_efficiency", std::to_string(*stats.efficiency));
666         }
667     }
668 
669     return stats;
670 }
671 
makeInstance(android_app * pApp)672 void Renderer::makeInstance(android_app *pApp) {
673     if (sRenderer == nullptr) {
674         sRenderer = std::make_unique<Renderer>(pApp);
675     }
676 }
677 
getInstance()678 Renderer *Renderer::getInstance() {
679     if (sRenderer == nullptr) {
680         return nullptr;
681     }
682     return sRenderer.get();
683 }
684 
685 std::unique_ptr<Renderer> Renderer::sRenderer = nullptr;