• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package android.gameperformance;
17 
18 import java.text.DecimalFormat;
19 import java.util.concurrent.TimeUnit;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.util.Log;
24 
25 /**
26  * Base class for a test that performs bisection to determine maximum
27  * performance of a metric test measures.
28  */
29 public abstract class BaseTest  {
30     private final static String TAG = "BaseTest";
31 
32     // Time to wait for render warm up. No statistics is collected during this pass.
33     private final static long WARM_UP_TIME = TimeUnit.SECONDS.toMillis(5);
34 
35     // Perform pass to probe the configuration using iterations. After each iteration current FPS is
36     // checked and if it looks obviously bad, pass gets stopped earlier. Once all iterations are
37     // done and final FPS is above PASS_THRESHOLD pass to probe is considered successful.
38     private final static long TEST_ITERATION_TIME = TimeUnit.SECONDS.toMillis(12);
39     private final static int TEST_ITERATION_COUNT = 5;
40 
41     // FPS pass test threshold, in ratio from ideal FPS, that matches device
42     // refresh rate.
43     private final static double PASS_THRESHOLD = 0.95;
44     // FPS threshold, in ratio from ideal FPS, to identify that current pass to probe is obviously
45     // bad and to stop pass earlier.
46     private final static double OBVIOUS_BAD_THRESHOLD = 0.90;
47 
48     private static DecimalFormat DOUBLE_FORMATTER = new DecimalFormat("#.##");
49 
50     private final GamePerformanceActivity mActivity;
51 
52     // Device's refresh rate.
53     private final double mRefreshRate;
54 
BaseTest(@onNull GamePerformanceActivity activity)55     public BaseTest(@NonNull GamePerformanceActivity activity) {
56         mActivity = activity;
57         mRefreshRate = activity.getDisplay().getRefreshRate();
58     }
59 
60     @NonNull
getContext()61     public Context getContext() {
62         return mActivity;
63     }
64 
65     @NonNull
getActivity()66     public GamePerformanceActivity getActivity() {
67         return mActivity;
68     }
69 
70     // Returns name of the test.
getName()71     public abstract String getName();
72 
73     // Returns unit name.
getUnitName()74     public abstract String getUnitName();
75 
76     // Returns number of measured units per one bisection unit.
getUnitScale()77     public abstract double getUnitScale();
78 
79     // Initializes test.
initUnits(double unitCount)80     public abstract void initUnits(double unitCount);
81 
82     // Initializes probe pass.
initProbePass(int probe)83     protected abstract void initProbePass(int probe);
84 
85     // Frees probe pass.
freeProbePass()86     protected abstract void freeProbePass();
87 
88     /**
89      * Performs the test and returns maximum number of measured units achieved. Unit is test
90      * specific and name is returned by getUnitName. Returns 0 in case of failure.
91      */
run()92     public double run() {
93         try {
94             Log.i(TAG, "Test started " + getName());
95 
96             final double passFps = PASS_THRESHOLD * mRefreshRate;
97             final double obviousBadFps = OBVIOUS_BAD_THRESHOLD * mRefreshRate;
98 
99             // Bisection bounds. Probe value is taken as middle point. Then it used to initialize
100             // test with probe * getUnitScale units. In case probe passed, lowLimit is updated to
101             // probe, otherwise upLimit is updated to probe. lowLimit contains probe that passes
102             // and upLimit contains the probe that fails. Each iteration narrows the range.
103             // Iterations continue until range is collapsed and lowLimit contains actual test
104             // result.
105             int lowLimit = 0;  // Initially 0, that is recognized as failure.
106             int upLimit = 250;
107 
108             while (true) {
109                 int probe = (lowLimit + upLimit) / 2;
110                 if (probe == lowLimit) {
111                     Log.i(TAG, "Test done: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
112                                " " + getUnitName());
113                     return probe * getUnitScale();
114                 }
115 
116                 Log.i(TAG, "Start probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + " " +
117                            getUnitName());
118                 initProbePass(probe);
119 
120                 Thread.sleep(WARM_UP_TIME);
121 
122                 getActivity().resetFrameTimes();
123 
124                 double fps = 0.0f;
125                 for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
126                     Thread.sleep(TEST_ITERATION_TIME);
127                     fps = getActivity().getFps();
128                     if (fps < obviousBadFps) {
129                         // Stop test earlier, we could not fit the loading.
130                         break;
131                     }
132                 }
133 
134                 freeProbePass();
135 
136                 Log.i(TAG, "Finish probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
137                            " " + getUnitName() + " - " + DOUBLE_FORMATTER.format(fps) + " FPS.");
138                 if (fps < passFps) {
139                     upLimit = probe;
140                 } else {
141                     lowLimit = probe;
142                 }
143             }
144         } catch (InterruptedException e) {
145             Thread.currentThread().interrupt();
146             return 0;
147         }
148     }
149 }