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;