blob: e7057565499c2ea31238411fdf43786feca996fa [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.gameperformance;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;
import android.annotation.NonNull;
import android.content.Context;
import android.util.Log;
/**
* Base class for a test that performs bisection to determine maximum
* performance of a metric test measures.
*/
public abstract class BaseTest {
private final static String TAG = "BaseTest";
// Time to wait for render warm up. No statistics is collected during this pass.
private final static long WARM_UP_TIME = TimeUnit.SECONDS.toMillis(5);
// Perform pass to probe the configuration using iterations. After each iteration current FPS is
// checked and if it looks obviously bad, pass gets stopped earlier. Once all iterations are
// done and final FPS is above PASS_THRESHOLD pass to probe is considered successful.
private final static long TEST_ITERATION_TIME = TimeUnit.SECONDS.toMillis(12);
private final static int TEST_ITERATION_COUNT = 5;
// FPS pass test threshold, in ratio from ideal FPS, that matches device
// refresh rate.
private final static double PASS_THRESHOLD = 0.95;
// FPS threshold, in ratio from ideal FPS, to identify that current pass to probe is obviously
// bad and to stop pass earlier.
private final static double OBVIOUS_BAD_THRESHOLD = 0.90;
private static DecimalFormat DOUBLE_FORMATTER = new DecimalFormat("#.##");
private final GamePerformanceActivity mActivity;
// Device's refresh rate.
private final double mRefreshRate;
public BaseTest(@NonNull GamePerformanceActivity activity) {
mActivity = activity;
mRefreshRate = activity.getDisplay().getRefreshRate();
}
@NonNull
public Context getContext() {
return mActivity;
}
@NonNull
public GamePerformanceActivity getActivity() {
return mActivity;
}
// Returns name of the test.
public abstract String getName();
// Returns unit name.
public abstract String getUnitName();
// Returns number of measured units per one bisection unit.
public abstract double getUnitScale();
// Initializes test.
public abstract void initUnits(double unitCount);
// Initializes probe pass.
protected abstract void initProbePass(int probe);
// Frees probe pass.
protected abstract void freeProbePass();
/**
* Performs the test and returns maximum number of measured units achieved. Unit is test
* specific and name is returned by getUnitName. Returns 0 in case of failure.
*/
public double run() {
try {
Log.i(TAG, "Test started " + getName());
final double passFps = PASS_THRESHOLD * mRefreshRate;
final double obviousBadFps = OBVIOUS_BAD_THRESHOLD * mRefreshRate;
// Bisection bounds. Probe value is taken as middle point. Then it used to initialize
// test with probe * getUnitScale units. In case probe passed, lowLimit is updated to
// probe, otherwise upLimit is updated to probe. lowLimit contains probe that passes
// and upLimit contains the probe that fails. Each iteration narrows the range.
// Iterations continue until range is collapsed and lowLimit contains actual test
// result.
int lowLimit = 0; // Initially 0, that is recognized as failure.
int upLimit = 250;
while (true) {
int probe = (lowLimit + upLimit) / 2;
if (probe == lowLimit) {
Log.i(TAG, "Test done: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
" " + getUnitName());
return probe * getUnitScale();
}
Log.i(TAG, "Start probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) + " " +
getUnitName());
initProbePass(probe);
Thread.sleep(WARM_UP_TIME);
getActivity().resetFrameTimes();
double fps = 0.0f;
for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
Thread.sleep(TEST_ITERATION_TIME);
fps = getActivity().getFps();
if (fps < obviousBadFps) {
// Stop test earlier, we could not fit the loading.
break;
}
}
freeProbePass();
Log.i(TAG, "Finish probe: " + DOUBLE_FORMATTER.format(probe * getUnitScale()) +
" " + getUnitName() + " - " + DOUBLE_FORMATTER.format(fps) + " FPS.");
if (fps < passFps) {
upLimit = probe;
} else {
lowLimit = probe;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return 0;
}
}
}