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 <GLES3/gl3.h>
19 #include <android/imagedecoder.h>
20 
21 #include <memory>
22 #include <numeric>
23 #include <string>
24 #include <vector>
25 // #include <iostream>
26 
27 #include <chrono>
28 
29 #include "AndroidOut.h"
30 #include "JNIManager.h"
31 #include "Shader.h"
32 #include "TextureAsset.h"
33 #include "Utility.h"
34 #include "android/performance_hint.h"
35 
36 using namespace std::chrono_literals;
37 //! executes glGetString and outputs the result to logcat
38 #define PRINT_GL_STRING(s) \
39     { aout << #s ": " << glGetString(s) << std::endl; }
40 
41 /*!
42  * @brief if glGetString returns a space separated list of elements, prints each one on a new line
43  *
44  * This works by creating an istringstream of the input c-style string. Then that is used to create
45  * a vector -- each element of the vector is a new element in the input string. Finally a foreach
46  * loop consumes this and outputs it to logcat using @a aout
47  */
48 #define PRINT_GL_STRING_AS_LIST(s)                                                 \
49     {                                                                              \
50         std::istringstream extensionStream((const char *)glGetString(s));          \
51         std::vector<std::string>                                                   \
52                 extensionList(std::istream_iterator<std::string>{extensionStream}, \
53                               std::istream_iterator<std::string>());               \
54         aout << #s ":\n";                                                          \
55         for (auto &extension : extensionList) {                                    \
56             aout << extension << "\n";                                             \
57         }                                                                          \
58         aout << std::endl;                                                         \
59     }
60 
61 //! Color for cornflower blue. Can be sent directly to glClearColor
62 #define CORNFLOWER_BLUE 100 / 255.f, 149 / 255.f, 237 / 255.f, 1
63 
64 // Vertex shader, you'd typically load this from assets
65 static const char *vertex = R"vertex(#version 300 es
66 in vec3 inPosition;
67 in vec2 inUV;
68 
69 out vec2 fragUV;
70 
71 uniform mat4 uProjection;
72 
73 void main() {
74     fragUV = inUV;
75     gl_Position = uProjection * vec4(inPosition, 1.0);
76 }
77 )vertex";
78 
79 // Fragment shader, you'd typically load this from assets
80 static const char *fragment = R"fragment(#version 300 es
81 precision mediump float;
82 
83 in vec2 fragUV;
84 
85 uniform sampler2D uTexture;
86 
87 out vec4 outColor;
88 
89 void main() {
90     outColor = texture(uTexture, fragUV);
91 }
92 )fragment";
93 
94 /*!
95  * Half the height of the projection matrix. This gives you a renderable area of height 4 ranging
96  * from -2 to 2
97  */
98 static constexpr float kProjectionHalfHeight = 2.f;
99 
100 /*!
101  * The near plane distance for the projection matrix. Since this is an orthographic projection
102  * matrix, it's convenient to have negative values for sorting (and avoiding z-fighting at 0).
103  */
104 static constexpr float kProjectionNearPlane = -1.f;
105 
106 /*!
107  * The far plane distance for the projection matrix. Since this is an orthographic porjection
108  * matrix, it's convenient to have the far plane equidistant from 0 as the near plane.
109  */
110 static constexpr float kProjectionFarPlane = 1.f;
111 
~Renderer()112 Renderer::~Renderer() {
113     if (display_ != EGL_NO_DISPLAY) {
114         eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
115         if (context_ != EGL_NO_CONTEXT) {
116             eglDestroyContext(display_, context_);
117             context_ = EGL_NO_CONTEXT;
118         }
119         if (surface_ != EGL_NO_SURFACE) {
120             eglDestroySurface(display_, surface_);
121             surface_ = EGL_NO_SURFACE;
122         }
123         eglTerminate(display_);
124         display_ = EGL_NO_DISPLAY;
125     }
126 }
127 
render()128 jlong Renderer::render() {
129     // Check to see if the surface has changed size. This is _necessary_ to do every frame when
130     // using immersive mode as you'll get no other notification that your renderable area has
131     // changed.
132 
133     updateRenderArea();
134     assert(display_ != nullptr);
135     assert(surface_ != nullptr);
136     assert(shader_ != nullptr);
137 
138     // When the renderable area changes, the fprojection matrix has to also be updated. This is true
139     // even if you change from the sample orthographic projection matrix as your aspect ratio has
140     // likely changed.
141     if (shaderNeedsNewProjectionMatrix_) {
142         // a placeholder projection matrix allocated on the stack. Column-major memory layout
143         float projectionMatrix[16] = {0};
144 
145         // build an orthographic projection matrix for 2d rendering
146         Utility::buildOrthographicMatrix(projectionMatrix, kProjectionHalfHeight,
147                                          float(width_) / height_, kProjectionNearPlane,
148                                          kProjectionFarPlane);
149 
150         // send the matrix to the shader
151         // Note: the shader must be active for this to work.
152         assert(projectionMatrix != nullptr);
153 
154         if (shader_ != nullptr) {
155             shader_->setProjectionMatrix(projectionMatrix);
156         }
157 
158         // make sure the matrix isn't generated every frame
159         shaderNeedsNewProjectionMatrix_ = false;
160     }
161 
162     // clear the color buffer
163     glClear(GL_COLOR_BUFFER_BIT);
164 
165     // Rotate the models
166     const std::chrono::steady_clock::duration rpm = 2s;
167     static std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
168     std::chrono::steady_clock::time_point renderTime = std::chrono::steady_clock::now();
169     // Figure out what angle the models need to be at
170     std::chrono::steady_clock::duration offset = (renderTime - startTime) % rpm;
171     auto spin = static_cast<double>(offset.count()) / static_cast<double>(rpm.count());
172 
173     // Render all the models. There's no depth testing in this sample so they're accepted in the
174     // order provided. But the sample EGL setup requests a 24 bit depth buffer so you could
175     // configure it at the end of initRenderer
176     auto start = std::chrono::steady_clock::now();
177 
178     if (!heads_.empty()) {
179         for (auto &model : heads_) {
180             model.setRotation(M_PI * 2.0 * spin);
181             shader_->drawModel(model);
182         }
183     }
184 
185     auto end = std::chrono::steady_clock::now();
186 
187     // Present the rendered image. This is an implicit glFlush.
188     auto swapResult = eglSwapBuffers(display_, surface_);
189     assert(swapResult == EGL_TRUE);
190     return (end - start).count();
191 }
192 
initRenderer()193 void Renderer::initRenderer() {
194     // Choose your render attributes
195     constexpr EGLint attribs[] = {EGL_RENDERABLE_TYPE,
196                                   EGL_OPENGL_ES3_BIT,
197                                   EGL_SURFACE_TYPE,
198                                   EGL_WINDOW_BIT,
199                                   EGL_BLUE_SIZE,
200                                   8,
201                                   EGL_GREEN_SIZE,
202                                   8,
203                                   EGL_RED_SIZE,
204                                   8,
205                                   EGL_DEPTH_SIZE,
206                                   24,
207                                   EGL_NONE};
208 
209     // The default display is probably what you want on Android
210     auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
211     eglInitialize(display, nullptr, nullptr);
212 
213     // figure out how many configs there are
214     EGLint numConfigs;
215     eglChooseConfig(display, attribs, nullptr, 0, &numConfigs);
216 
217     // get the list of configurations
218     std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
219     eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
220 
221     // Find a config we like.
222     // Could likely just grab the first if we don't care about anything else in the config.
223     // Otherwise hook in your own heuristic
224     auto config =
225             *std::find_if(supportedConfigs.get(), supportedConfigs.get() + numConfigs,
226                           [&display](const EGLConfig &config) {
227                               EGLint red, green, blue, depth;
228                               if (eglGetConfigAttrib(display, config, EGL_RED_SIZE, &red) &&
229                                   eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &green) &&
230                                   eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &blue) &&
231                                   eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &depth)) {
232                                   aout << "Found config with " << red << ", " << green << ", "
233                                        << blue << ", " << depth << std::endl;
234                                   return red == 8 && green == 8 && blue == 8 && depth == 24;
235                               }
236                               return false;
237                           });
238 
239     // create the proper window surface
240     EGLint format;
241     eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
242     EGLSurface surface = eglCreateWindowSurface(display, config, app_->window, nullptr);
243 
244     // Create a GLES 3 context
245     EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
246     EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);
247 
248     // get some window metrics
249     auto madeCurrent = eglMakeCurrent(display, surface, surface, context);
250     assert(madeCurrent);
251 
252     display_ = display;
253     surface_ = surface;
254     context_ = context;
255 
256     // make width and height invalid so it gets updated the first frame in @a updateRenderArea()
257     width_ = -1;
258     height_ = -1;
259 
260     PRINT_GL_STRING(GL_VENDOR);
261     PRINT_GL_STRING(GL_RENDERER);
262     PRINT_GL_STRING(GL_VERSION);
263     PRINT_GL_STRING_AS_LIST(GL_EXTENSIONS);
264 
265     shader_ = std::unique_ptr<Shader>(
266             Shader::loadShader(vertex, fragment, "inPosition", "inUV", "uProjection"));
267     assert(shader_);
268 
269     // Note: there's only one shader in this demo, so I'll activate it here. For a more complex game
270     // you'll want to track the active shader and activate/deactivate it as necessary
271     shader_->activate();
272 
273     // setup any other gl related global states
274     glClearColor(CORNFLOWER_BLUE);
275 
276     // enable alpha globally for now, you probably don't want to do this in a game
277     glEnable(GL_BLEND);
278     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
279 
280     // get some demo models into memory
281     // setNumHeads(1000);
282 }
283 
updateRenderArea()284 void Renderer::updateRenderArea() {
285     EGLint width;
286     eglQuerySurface(display_, surface_, EGL_WIDTH, &width);
287 
288     EGLint height;
289     eglQuerySurface(display_, surface_, EGL_HEIGHT, &height);
290 
291     if (width != width_ || height != height_) {
292         width_ = width;
293         height_ = height;
294         glViewport(0, 0, width, height);
295 
296         // make sure that we lazily recreate the projection matrix before we render
297         shaderNeedsNewProjectionMatrix_ = true;
298     }
299 }
300 
addHead()301 void Renderer::addHead() {
302     thread_local auto assetManager = app_->activity->assetManager;
303     thread_local auto spAndroidRobotTexture = TextureAsset::loadAsset(assetManager, "android.png");
304     thread_local std::vector<Vertex> vertices = {
305             Vertex(Vector3{{0.3, 0.3, 0}}, Vector2{{0, 0}}),   // 0
306             Vertex(Vector3{{-0.3, 0.3, 0}}, Vector2{{1, 0}}),  // 1
307             Vertex(Vector3{{-0.3, -0.3, 0}}, Vector2{{1, 1}}), // 2
308             Vertex(Vector3{{0.3, -0.3, 0}}, Vector2{{0, 1}})   // 3
309     };
310     thread_local std::vector<Index> indices = {0, 1, 2, 0, 2, 3};
311     thread_local Model baseModel{vertices, indices, spAndroidRobotTexture};
312     float angle = 2 * M_PI * (static_cast<float>(rand()) / static_cast<float>(RAND_MAX));
313     float x = 1.5 * static_cast<float>(rand()) / static_cast<float>(RAND_MAX) - 0.75;
314     float y = 3.0 * static_cast<float>(rand()) / static_cast<float>(RAND_MAX) - 1.5;
315     Vector3 offset{{x, y, 0}};
316     Model toAdd{baseModel};
317     toAdd.move(offset);
318     toAdd.setRotationOffset(angle);
319     heads_.push_back(toAdd);
320 }
321 
setNumHeads(int headCount)322 void Renderer::setNumHeads(int headCount) {
323     if (headCount > heads_.size()) {
324         int to_add = headCount - heads_.size();
325         for (int i = 0; i < to_add; ++i) {
326             addHead();
327         }
328     } else if (headCount < heads_.size()) {
329         heads_.erase(heads_.begin() + headCount, heads_.end());
330     }
331 }
332 
getAdpfSupported()333 bool Renderer::getAdpfSupported() {
334     if (hintManager_ == nullptr) {
335         hintManager_ = APerformanceHint_getManager();
336     }
337     long preferredRate = APerformanceHint_getPreferredUpdateRateNanos(hintManager_);
338     results_["isHintSessionSupported"] = preferredRate < 0 ? "false" : "true";
339     results_["preferredRate"] = std::to_string(preferredRate);
340     return preferredRate >= 0;
341 }
342 
startHintSession(std::vector<int32_t> & tids,int64_t target)343 void Renderer::startHintSession(std::vector<int32_t> &tids, int64_t target) {
344     if (hintManager_ == nullptr) {
345         hintManager_ = APerformanceHint_getManager();
346     }
347     if (hintSession_ == nullptr && hintManager_ != nullptr) {
348         lastTarget_ = target;
349         hintSession_ =
350                 APerformanceHint_createSession(hintManager_, tids.data(), tids.size(), target);
351         if (hintSession_ == nullptr) {
352             Utility::setFailure("Failed to create session", this);
353         }
354     }
355 }
356 
reportActualWorkDuration(int64_t duration)357 void Renderer::reportActualWorkDuration(int64_t duration) {
358     if (isHintSessionRunning()) {
359         int ret = APerformanceHint_reportActualWorkDuration(hintSession_, duration);
360         if (ret < 0) {
361             Utility::setFailure("Failed to report actual work duration with code " +
362                                         std::to_string(ret),
363                                 this);
364         }
365     }
366 }
367 
updateTargetWorkDuration(int64_t target)368 void Renderer::updateTargetWorkDuration(int64_t target) {
369     lastTarget_ = target;
370     if (isHintSessionRunning()) {
371         int ret = APerformanceHint_updateTargetWorkDuration(hintSession_, target);
372         if (ret < 0) {
373             Utility::setFailure("Failed to update target duration with code " + std::to_string(ret),
374                                 this);
375         }
376     }
377 }
378 
getTargetWorkDuration()379 int64_t Renderer::getTargetWorkDuration() {
380     return lastTarget_;
381 }
382 
isHintSessionRunning()383 bool Renderer::isHintSessionRunning() {
384     return hintSession_ != nullptr;
385 }
386 
closeHintSession()387 void Renderer::closeHintSession() {
388     APerformanceHint_closeSession(hintSession_);
389 }
390 
addResult(std::string name,std::string value)391 void Renderer::addResult(std::string name, std::string value) {
392     results_[name] = value;
393 }
394 
getResults()395 std::map<std::string, std::string> &Renderer::getResults() {
396     return results_;
397 }
398 
setBaselineMedian(int64_t median)399 void Renderer::setBaselineMedian(int64_t median) {
400     baselineMedian_ = median;
401 }
402 
403 template <typename T>
getMedian(std::vector<T> values)404 T getMedian(std::vector<T> values) {
405     std::sort(values.begin(), values.end());
406     return values[values.size() / 2];
407 }
408 
getFrameStats(std::vector<int64_t> & durations,std::vector<int64_t> & intervals,std::string & testName)409 FrameStats Renderer::getFrameStats(std::vector<int64_t> &durations, std::vector<int64_t> &intervals,
410                                    std::string &testName) {
411     FrameStats stats;
412     // Double precision is int-precise up to 2^52 so we should be fine for this range
413     double sum = std::accumulate(durations.begin(), durations.end(), 0);
414     double mean = static_cast<double>(sum) / static_cast<double>(durations.size());
415     int dropCount = 0;
416     double varianceSum = 0;
417     for (int64_t &duration : durations) {
418         if (isHintSessionRunning() && duration > lastTarget_) {
419             ++dropCount;
420         }
421         varianceSum += (duration - mean) * (duration - mean);
422     }
423     if (durations.size() > 0) {
424         stats.medianWorkDuration = getMedian(durations);
425     }
426     if (intervals.size() > 0) {
427         stats.medianFrameInterval = getMedian(intervals);
428     }
429     stats.deviation = std::sqrt(varianceSum / static_cast<double>(durations.size() - 1));
430     if (isHintSessionRunning()) {
431         stats.exceededCount = dropCount;
432         stats.exceededFraction =
433                 static_cast<double>(dropCount) / static_cast<double>(durations.size());
434         stats.efficiency = static_cast<double>(sum) /
435                 static_cast<double>(durations.size() * std::min(lastTarget_, baselineMedian_));
436     }
437 
438     if (testName.size() > 0) {
439         addResult(testName + "_median", std::to_string(stats.medianWorkDuration));
440         addResult(testName + "_median_interval", std::to_string(stats.medianFrameInterval));
441         addResult(testName + "_deviation", std::to_string(stats.deviation));
442         if (isHintSessionRunning()) {
443             addResult(testName + "_target", std::to_string(getTargetWorkDuration()));
444             addResult(testName + "_target_exceeded_count", std::to_string(*stats.exceededCount));
445             addResult(testName + "_target_exceeded_fraction",
446                       std::to_string(*stats.exceededFraction));
447             addResult(testName + "_efficiency", std::to_string(*stats.efficiency));
448         }
449     }
450 
451     return stats;
452 }
453