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 }