arc: Extends cheets_GamePerfmance test to capture more metrics.

This adds extra metrics per each frame to capture:
  * Maximum number of triangles to render.
  * Fill and blend rate in kpixels
  * Maximum number of device calls to render.
  * Maximum number of UI controls.
This also capture results in two mode, first mode without extra load
and second mode with extra 2 threads that load CPU and emulate heavy
app/game.

Test: Locally
Bug: 13553231
Bug: 1347063273
Change-Id: I87491634fa38bd5e04d47d62154a0da8e467213f
(cherry picked from commit f3dae277aa990dcb99d5a0bab1f9ccc141b9e4d9)
This commit is contained in:
khmel@google.com
2019-06-21 08:49:03 -07:00
committed by Yury Khmel
parent 652e05f0ff
commit e41ec354bb
28 changed files with 1461 additions and 26 deletions

View File

@@ -16,7 +16,9 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.gameperformance">
package="android.gameperformance"
android:versionCode="3"
android:versionName="3.0" >
<uses-sdk android:minSdkVersion="25"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
@@ -24,7 +26,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:theme="@style/noeffects">
<uses-library android:name="android.test.runner" />
<activity android:name="android.gameperformance.GamePerformanceActivity" >
<activity android:name="android.gameperformance.GamePerformanceActivity"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -0,0 +1,29 @@
<!--
* 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.
-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/animation" android:oneshot="false">
<item android:drawable="@drawable/digit_0" android:duration="15" />
<item android:drawable="@drawable/digit_1" android:duration="15" />
<item android:drawable="@drawable/digit_2" android:duration="15" />
<item android:drawable="@drawable/digit_3" android:duration="15" />
<item android:drawable="@drawable/digit_4" android:duration="15" />
<item android:drawable="@drawable/digit_5" android:duration="15" />
<item android:drawable="@drawable/digit_6" android:duration="15" />
<item android:drawable="@drawable/digit_7" android:duration="15" />
<item android:drawable="@drawable/digit_8" android:duration="15" />
<item android:drawable="@drawable/digit_9" android:duration="15" />
</animation-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,152 @@
/*
* 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;
import android.view.WindowManager;
/**
* 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;
final WindowManager windowManager =
(WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
mRefreshRate = windowManager.getDefaultDisplay().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;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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;
/**
* Ballast thread that emulates CPU load by performing heavy computation in loop.
*/
public class CPULoadThread extends Thread {
private boolean mStopRequest;
public CPULoadThread() {
mStopRequest = false;
}
private static double computePi() {
double accumulator = 0;
double prevAccumulator = -1;
int index = 1;
while (true) {
accumulator += ((1.0 / (2.0 * index - 1)) - (1.0 / (2.0 * index + 1)));
if (accumulator == prevAccumulator) {
break;
}
prevAccumulator = accumulator;
index += 2;
}
return 4 * accumulator;
}
// Requests thread to stop.
public void issueStopRequest() {
synchronized (this) {
mStopRequest = true;
}
}
@Override
public void run() {
// Load CPU by PI computation.
while (computePi() != 0) {
synchronized (this) {
if (mStopRequest) {
break;
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 android.annotation.NonNull;
/**
* Tests that verifies how many UI controls can be handled to keep FPS close to device refresh rate.
* As a test UI control ImageView with an infinite animation is chosen. The animation has refresh
* rate ~67Hz that forces all devices to refresh UI at highest possible rate.
*/
public class ControlsTest extends BaseTest {
public ControlsTest(@NonNull GamePerformanceActivity activity) {
super(activity);
}
@NonNull
public CustomControlView getView() {
return getActivity().getControlView();
}
@Override
protected void initProbePass(int probe) {
try {
getActivity().attachControlView();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
initUnits(probe * getUnitScale());
}
@Override
protected void freeProbePass() {
}
@Override
public String getName() {
return "control_count";
}
@Override
public String getUnitName() {
return "controls";
}
@Override
public double getUnitScale() {
return 5.0;
}
@Override
public void initUnits(double controlCount) {
try {
getView().createControls(getActivity(), (int)Math.round(controlCount));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.AnimationDrawable;
import android.util.Log;
import android.view.WindowManager;
import android.widget.AbsoluteLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
/**
* View that holds requested number of UI controls as ImageView with an infinite animation.
*/
public class CustomControlView extends AbsoluteLayout {
private final static int CONTROL_DIMENTION = 48;
private final int mPerRowControlCount;
private List<Long> mFrameTimes = new ArrayList<>();
public CustomControlView(@NonNull Context context) {
super(context);
final WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mPerRowControlCount = windowManager.getDefaultDisplay().getWidth() / CONTROL_DIMENTION;
}
/**
* Helper class that overrides ImageView and observes draw requests. Only
* one such control is created which is the first control in the view.
*/
class ReferenceImageView extends ImageView {
public ReferenceImageView(Context context) {
super(context);
}
@Override
public void draw(Canvas canvas) {
reportFrame();
super.draw(canvas);
}
}
@WorkerThread
public void createControls(
@NonNull Activity activity, int controlCount) throws InterruptedException {
synchronized (this) {
final CountDownLatch latch = new CountDownLatch(1);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
removeAllViews();
for (int i = 0; i < controlCount; ++i) {
final ImageView image = (i == 0) ?
new ReferenceImageView(activity) : new ImageView(activity);
final int x = (i % mPerRowControlCount) * CONTROL_DIMENTION;
final int y = (i / mPerRowControlCount) * CONTROL_DIMENTION;
final AbsoluteLayout.LayoutParams layoutParams =
new AbsoluteLayout.LayoutParams(
CONTROL_DIMENTION, CONTROL_DIMENTION, x, y);
image.setLayoutParams(layoutParams);
image.setBackgroundResource(R.drawable.animation);
final AnimationDrawable animation =
(AnimationDrawable)image.getBackground();
animation.start();
addView(image);
}
latch.countDown();
}
});
latch.await();
}
}
@MainThread
private void reportFrame() {
final long time = System.currentTimeMillis();
synchronized (mFrameTimes) {
mFrameTimes.add(time);
}
}
/**
* Resets frame times in order to calculate FPS for the different test pass.
*/
public void resetFrameTimes() {
synchronized (mFrameTimes) {
mFrameTimes.clear();
}
}
/**
* Returns current FPS based on collected frame times.
*/
public double getFps() {
synchronized (mFrameTimes) {
if (mFrameTimes.size() < 2) {
return 0.0f;
}
return 1000.0 * mFrameTimes.size() /
(mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
}
}
}

View File

@@ -17,23 +17,36 @@ package android.gameperformance;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
public class CustomOpenGLView extends GLSurfaceView {
private Random mRandom;
private List<Long> mFrameTimes;
public final static String TAG = "CustomOpenGLView";
public CustomOpenGLView(Context context) {
private final List<Long> mFrameTimes;
private final Object mLock = new Object();
private boolean mRenderReady = false;
private FrameDrawer mFrameDrawer = null;
private float mRenderRatio;
private int mRenderWidth;
private int mRenderHeight;
public interface FrameDrawer {
public void drawFrame(@NonNull GL10 gl);
}
public CustomOpenGLView(@NonNull Context context) {
super(context);
mRandom = new Random();
mFrameTimes = new ArrayList<Long>();
setEGLContextClientVersion(2);
@@ -41,25 +54,35 @@ public class CustomOpenGLView extends GLSurfaceView {
setRenderer(new GLSurfaceView.Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(TAG, "SurfaceCreated: " + config);
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_NICEST); }
GL10.GL_NICEST);
synchronized (mLock) {
mRenderReady = true;
mLock.notify();
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.i(TAG, "SurfaceChanged: " + width + "x" + height);
GLES20.glViewport(0, 0, width, height);
setRenderBounds(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClearColor(
mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat(), 1.0f);
GLES20.glClearColor(0.25f, 0.25f, 0.25f, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
synchronized (mFrameTimes) {
synchronized (mLock) {
if (mFrameDrawer != null) {
mFrameDrawer.drawFrame(gl);
}
mFrameTimes.add(System.currentTimeMillis());
}
}
@@ -67,20 +90,38 @@ public class CustomOpenGLView extends GLSurfaceView {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
public void setRenderBounds(int width, int height) {
mRenderWidth = width;
mRenderHeight = height;
mRenderRatio = (float) mRenderWidth / mRenderHeight;
}
public float getRenderRatio() {
return mRenderRatio;
}
public int getRenderWidth() {
return mRenderWidth;
}
public int getRenderHeight() {
return mRenderHeight;
}
/**
* Resets frame times in order to calculate fps for different test pass.
* Resets frame times in order to calculate FPS for the different test pass.
*/
public void resetFrameTimes() {
synchronized (mFrameTimes) {
synchronized (mLock) {
mFrameTimes.clear();
}
}
/**
* Returns current fps based on collected frame times.
* Returns current FPS based on collected frame times.
*/
public double getFps() {
synchronized (mFrameTimes) {
synchronized (mLock) {
if (mFrameTimes.size() < 2) {
return 0.0f;
}
@@ -88,4 +129,26 @@ public class CustomOpenGLView extends GLSurfaceView {
(mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
}
}
/**
* Waits for render attached to the view.
*/
public void waitRenderReady() {
synchronized (mLock) {
while (!mRenderReady) {
try {
mLock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* Sets/resets frame drawer.
*/
public void setFrameDrawer(@Nullable FrameDrawer frameDrawer) {
mFrameDrawer = frameDrawer;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.util.ArrayList;
import java.util.List;
import android.annotation.NonNull;
/**
* Tests that verifies maximum number of device calls to render the geometry to keep FPS close to
* the device refresh rate. This uses trivial one triangle patch that is rendered multiple times.
*/
public class DeviceCallsOpenGLTest extends RenderPatchOpenGLTest {
public DeviceCallsOpenGLTest(@NonNull GamePerformanceActivity activity) {
super(activity);
}
@Override
public String getName() {
return "device_calls";
}
@Override
public String getUnitName() {
return "calls";
}
@Override
public double getUnitScale() {
return 25.0;
}
@Override
public void initUnits(double deviceCallsD) {
final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
final RenderPatch renderPatch = new RenderPatch(1 /* triangleCount */,
0.05f /* dimension */,
RenderPatch.TESSELLATION_BASE);
final int deviceCalls = (int)Math.round(deviceCallsD);
for (int i = 0; i < deviceCalls; ++i) {
renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
}
setRenderPatches(renderPatches);
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.util.ArrayList;
import java.util.List;
import javax.microedition.khronos.opengles.GL;
import android.annotation.NonNull;
import android.opengl.GLES20;
/**
* Tests that verifies maximum fill rate per frame can be used to keep FPS close to the device
* refresh rate. It works in two modes, blend disabled and blend enabled. This uses few big simple
* quad patches.
*/
public class FillRateOpenGLTest extends RenderPatchOpenGLTest {
private final float[] BLEND_COLOR = new float[] { 1.0f, 1.0f, 1.0f, 0.2f };
private final boolean mTestBlend;
public FillRateOpenGLTest(@NonNull GamePerformanceActivity activity, boolean testBlend) {
super(activity);
mTestBlend = testBlend;
}
@Override
public String getName() {
return mTestBlend ? "blend_rate" : "fill_rate";
}
@Override
public String getUnitName() {
return "screens";
}
@Override
public double getUnitScale() {
return 0.2;
}
@Override
public void initUnits(double screens) {
final CustomOpenGLView view = getView();
final int pixelRate = (int)Math.round(screens * view.getHeight() * view.getWidth());
final int maxPerPath = view.getHeight() * view.getHeight();
final int patchCount = (int)(pixelRate + maxPerPath -1) / maxPerPath;
final float patchDimension =
(float)((Math.sqrt(2.0f) * pixelRate / patchCount) / maxPerPath);
final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
final RenderPatch renderPatch = new RenderPatch(2 /* triangleCount for quad */,
patchDimension,
RenderPatch.TESSELLATION_BASE);
for (int i = 0; i < patchCount; ++i) {
renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
}
setRenderPatches(renderPatches);
}
@Override
public float[] getColor() {
return BLEND_COLOR;
}
@Override
public void onBeforeDraw(GL gl) {
if (!mTestBlend) {
return;
}
// Enable blend if needed.
GLES20.glEnable(GLES20.GL_BLEND);
OpenGLUtils.checkGlError("disableBlend");
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
OpenGLUtils.checkGlError("blendFunction");
}
}

View File

@@ -25,14 +25,32 @@ import android.view.WindowManager;
import android.widget.RelativeLayout;
/**
* Minimal activity that holds SurfaceView or GLSurfaceView.
* call attachSurfaceView or attachOpenGLView to switch views.
* Minimal activity that holds different types of views.
* call attachSurfaceView, attachOpenGLView or attachControlView to switch
* the view.
*/
public class GamePerformanceActivity extends Activity {
private CustomSurfaceView mSurfaceView = null;
private CustomOpenGLView mOpenGLView = null;
private CustomControlView mControlView = null;
private RelativeLayout mRootLayout;
private void detachAllViews() {
if (mOpenGLView != null) {
mRootLayout.removeView(mOpenGLView);
mOpenGLView = null;
}
if (mSurfaceView != null) {
mRootLayout.removeView(mSurfaceView);
mSurfaceView = null;
}
if (mControlView != null) {
mRootLayout.removeView(mControlView);
mControlView = null;
}
}
public void attachSurfaceView() throws InterruptedException {
synchronized (mRootLayout) {
if (mSurfaceView != null) {
@@ -42,10 +60,7 @@ public class GamePerformanceActivity extends Activity {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mOpenGLView != null) {
mRootLayout.removeView(mOpenGLView);
mOpenGLView = null;
}
detachAllViews();
mSurfaceView = new CustomSurfaceView(GamePerformanceActivity.this);
mRootLayout.addView(mSurfaceView);
latch.countDown();
@@ -65,10 +80,7 @@ public class GamePerformanceActivity extends Activity {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mSurfaceView != null) {
mRootLayout.removeView(mSurfaceView);
mSurfaceView = null;
}
detachAllViews();
mOpenGLView = new CustomOpenGLView(GamePerformanceActivity.this);
mRootLayout.addView(mOpenGLView);
latch.countDown();
@@ -78,6 +90,40 @@ public class GamePerformanceActivity extends Activity {
}
}
public void attachControlView() throws InterruptedException {
synchronized (mRootLayout) {
if (mControlView != null) {
return;
}
final CountDownLatch latch = new CountDownLatch(1);
runOnUiThread(new Runnable() {
@Override
public void run() {
detachAllViews();
mControlView = new CustomControlView(GamePerformanceActivity.this);
mRootLayout.addView(mControlView);
latch.countDown();
}
});
latch.await();
}
}
public CustomOpenGLView getOpenGLView() {
if (mOpenGLView == null) {
throw new RuntimeException("OpenGL view is not attached");
}
return mOpenGLView;
}
public CustomControlView getControlView() {
if (mControlView == null) {
throw new RuntimeException("Control view is not attached");
}
return mControlView;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -105,6 +151,8 @@ public class GamePerformanceActivity extends Activity {
mSurfaceView.resetFrameTimes();
} else if (mOpenGLView != null) {
mOpenGLView.resetFrameTimes();
} else if (mControlView != null) {
mControlView.resetFrameTimes();
} else {
throw new IllegalStateException("Nothing attached");
}
@@ -115,6 +163,8 @@ public class GamePerformanceActivity extends Activity {
return mSurfaceView.getFps();
} else if (mOpenGLView != null) {
return mOpenGLView.getFps();
} else if (mControlView != null) {
return mControlView.getFps();
} else {
throw new IllegalStateException("Nothing attached");
}

View File

@@ -17,14 +17,18 @@ package android.gameperformance;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Trace;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
@@ -84,4 +88,50 @@ public class GamePerformanceTest extends
getInstrumentation().sendStatus(Activity.RESULT_OK, status);
}
@SmallTest
public void testPerformanceMetricsWithoutExtraLoad() throws IOException, InterruptedException {
final Bundle status = runPerformanceTests("no_extra_load_");
getInstrumentation().sendStatus(Activity.RESULT_OK, status);
}
@SmallTest
public void testPerformanceMetricsWithExtraLoad() throws IOException, InterruptedException {
// Start CPU ballast threads first.
CPULoadThread[] cpuLoadThreads = new CPULoadThread[2];
for (int i = 0; i < cpuLoadThreads.length; ++i) {
cpuLoadThreads[i] = new CPULoadThread();
cpuLoadThreads[i].start();
}
final Bundle status = runPerformanceTests("extra_load_");
for (int i = 0; i < cpuLoadThreads.length; ++i) {
cpuLoadThreads[i].issueStopRequest();
cpuLoadThreads[i].join();
}
getInstrumentation().sendStatus(Activity.RESULT_OK, status);
}
@NonNull
private Bundle runPerformanceTests(@NonNull String prefix) {
final Bundle status = new Bundle();
final GamePerformanceActivity activity = getActivity();
final List<BaseTest> tests = new ArrayList<>();
tests.add(new TriangleCountOpenGLTest(activity));
tests.add(new FillRateOpenGLTest(activity, false /* testBlend */));
tests.add(new FillRateOpenGLTest(activity, true /* testBlend */));
tests.add(new DeviceCallsOpenGLTest(activity));
tests.add(new ControlsTest(activity));
for (BaseTest test : tests) {
final double result = test.run();
status.putDouble(prefix + test.getName(), result);
}
return status;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import android.annotation.NonNull;
import android.gameperformance.CustomOpenGLView.FrameDrawer;
/**
* Base class for all OpenGL based tests.
*/
public abstract class OpenGLTest extends BaseTest {
public OpenGLTest(@NonNull GamePerformanceActivity activity) {
super(activity);
}
@NonNull
public CustomOpenGLView getView() {
return getActivity().getOpenGLView();
}
// Performs test drawing.
protected abstract void draw(GL gl);
// Initializes the test on first draw call.
private class ParamFrameDrawer implements FrameDrawer {
private final double mUnitCount;
private boolean mInited;
public ParamFrameDrawer(double unitCount) {
mUnitCount = unitCount;
mInited = false;
}
@Override
public void drawFrame(GL10 gl) {
if (!mInited) {
initUnits(mUnitCount);
mInited = true;
}
draw(gl);
}
}
@Override
protected void initProbePass(int probe) {
try {
getActivity().attachOpenGLView();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
getView().waitRenderReady();
getView().setFrameDrawer(new ParamFrameDrawer(probe * getUnitScale()));
}
@Override
protected void freeProbePass() {
getView().setFrameDrawer(null);
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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 android.annotation.NonNull;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
/**
* Helper class for OpenGL.
*/
public class OpenGLUtils {
private final static String TAG = "OpenGLUtils";
public static void checkGlError(String glOperation) {
final int error = GLES20.glGetError();
if (error == GLES20.GL_NO_ERROR) {
return;
}
final String errorMessage = glOperation + ": glError " + error;
Log.e(TAG, errorMessage);
}
public static int loadShader(int type, String shaderCode) {
final int shader = GLES20.glCreateShader(type);
checkGlError("createShader");
GLES20.glShaderSource(shader, shaderCode);
checkGlError("shaderSource");
GLES20.glCompileShader(shader);
checkGlError("shaderCompile");
return shader;
}
public static int createProgram(@NonNull String vertexShaderCode,
@NonNull String fragmentShaderCode) {
final int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
final int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
final int program = GLES20.glCreateProgram();
checkGlError("createProgram");
GLES20.glAttachShader(program, vertexShader);
checkGlError("attachVertexShader");
GLES20.glAttachShader(program, fragmentShader);
checkGlError("attachFragmentShader");
GLES20.glLinkProgram(program);
checkGlError("linkProgram");
return program;
}
public static int createTexture(@NonNull Context context, int resource) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
OpenGLUtils.checkGlError("GenTextures");
final int handle = textureHandle[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, handle);
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLUtils.texImage2D(
GLES20.GL_TEXTURE_2D,
0,
BitmapFactory.decodeResource(
context.getResources(), resource, options),
0);
return handle;
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Helper class that generates patch to render. Patch is a regular polygon with the center in 0.
* Regular polygon fits in circle with requested radius.
*/
public class RenderPatch {
public static final int FLOAT_SIZE = 4;
public static final int SHORT_SIZE = 2;
public static final int VERTEX_COORD_COUNT = 3;
public static final int VERTEX_STRIDE = VERTEX_COORD_COUNT * FLOAT_SIZE;
public static final int TEXTURE_COORD_COUNT = 2;
public static final int TEXTURE_STRIDE = TEXTURE_COORD_COUNT * FLOAT_SIZE;
// Tessellation is done using points on circle.
public static final int TESSELLATION_BASE = 0;
// Tesselation is done using extra point in 0.
public static final int TESSELLATION_TO_CENTER = 1;
// Radius of circle that fits polygon.
private final float mDimension;
private final ByteBuffer mVertexBuffer;
private final ByteBuffer mTextureBuffer;
private final ByteBuffer mIndexBuffer;
public RenderPatch(int triangleCount, float dimension, int tessellation) {
mDimension = dimension;
int pointCount;
int externalPointCount;
if (triangleCount < 1) {
throw new IllegalArgumentException("Too few triangles to perform tessellation");
}
switch (tessellation) {
case TESSELLATION_BASE:
externalPointCount = triangleCount + 2;
pointCount = externalPointCount;
break;
case TESSELLATION_TO_CENTER:
if (triangleCount < 3) {
throw new IllegalArgumentException(
"Too few triangles to perform tessellation to center");
}
externalPointCount = triangleCount;
pointCount = triangleCount + 1;
break;
default:
throw new IllegalArgumentException("Wrong tesselation requested");
}
if (pointCount > Short.MAX_VALUE) {
throw new IllegalArgumentException("Number of requested triangles is too big");
}
mVertexBuffer = ByteBuffer.allocateDirect(pointCount * VERTEX_STRIDE);
mVertexBuffer.order(ByteOrder.nativeOrder());
mTextureBuffer = ByteBuffer.allocateDirect(pointCount * TEXTURE_STRIDE);
mTextureBuffer.order(ByteOrder.nativeOrder());
for (int i = 0; i < externalPointCount; ++i) {
// Use 45 degree rotation to make quad aligned along axises in case
// triangleCount is four.
final double angle = Math.PI * 0.25 + (Math.PI * 2.0 * i) / (externalPointCount);
// Positions
mVertexBuffer.putFloat((float) (dimension * Math.sin(angle)));
mVertexBuffer.putFloat((float) (dimension * Math.cos(angle)));
mVertexBuffer.putFloat(0.0f);
// Texture coordinates.
mTextureBuffer.putFloat((float) (0.5 + 0.5 * Math.sin(angle)));
mTextureBuffer.putFloat((float) (0.5 - 0.5 * Math.cos(angle)));
}
if (tessellation == TESSELLATION_TO_CENTER) {
// Add center point.
mVertexBuffer.putFloat(0.0f);
mVertexBuffer.putFloat(0.0f);
mVertexBuffer.putFloat(0.0f);
mTextureBuffer.putFloat(0.5f);
mTextureBuffer.putFloat(0.5f);
}
mIndexBuffer =
ByteBuffer.allocateDirect(
triangleCount * 3 /* indices per triangle */ * SHORT_SIZE);
mIndexBuffer.order(ByteOrder.nativeOrder());
switch (tessellation) {
case TESSELLATION_BASE:
for (int i = 0; i < triangleCount; ++i) {
mIndexBuffer.putShort((short) 0);
mIndexBuffer.putShort((short) (i + 1));
mIndexBuffer.putShort((short) (i + 2));
}
break;
case TESSELLATION_TO_CENTER:
for (int i = 0; i < triangleCount; ++i) {
mIndexBuffer.putShort((short)i);
mIndexBuffer.putShort((short)((i + 1) % externalPointCount));
mIndexBuffer.putShort((short)externalPointCount);
}
break;
}
if (mVertexBuffer.remaining() != 0 || mTextureBuffer.remaining() != 0 || mIndexBuffer.remaining() != 0) {
throw new RuntimeException("Failed to fill buffers");
}
mVertexBuffer.position(0);
mTextureBuffer.position(0);
mIndexBuffer.position(0);
}
public float getDimension() {
return mDimension;
}
public ByteBuffer getVertexBuffer() {
return mVertexBuffer;
}
public ByteBuffer getTextureBuffer() {
return mTextureBuffer;
}
public ByteBuffer getIndexBuffer() {
return mIndexBuffer;
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.util.Random;
import android.annotation.NonNull;
import android.opengl.Matrix;
/**
* Class that performs bouncing animation for RenderPatch on the screen.
*/
public class RenderPatchAnimation {
private final static Random RANDOM = new Random();
private final RenderPatch mRenderPatch;
// Bounds of animation
private final float mAvailableX;
private final float mAvailableY;
// Crurrent position.
private float mPosX;
private float mPosY;
// Direction of movement.
private float mDirX;
private float mDirY;
private float[] mMatrix;
public RenderPatchAnimation(@NonNull RenderPatch renderPatch, float ratio) {
mRenderPatch = renderPatch;
mAvailableX = ratio - mRenderPatch.getDimension();
mAvailableY = 1.0f - mRenderPatch.getDimension();
mPosX = 2.0f * mAvailableX * RANDOM.nextFloat() - mAvailableX;
mPosY = 2.0f * mAvailableY * RANDOM.nextFloat() - mAvailableY;
mMatrix = new float[16];
// Evenly distributed in cycle, normalized.
while (true) {
mDirX = 2.0f * RANDOM.nextFloat() - 1.0f;
mDirY = mRenderPatch.getDimension() < 1.0f ? 2.0f * RANDOM.nextFloat() - 1.0f : 0.0f;
final float length = (float)Math.sqrt(mDirX * mDirX + mDirY * mDirY);
if (length <= 1.0f && length > 0.0f) {
mDirX /= length;
mDirY /= length;
break;
}
}
}
@NonNull
public RenderPatch getRenderPatch() {
return mRenderPatch;
}
/**
* Performs the next update. t specifies the distance to travel along the direction. This checks
* if patch goes out of screen and invert axis direction if needed.
*/
public void update(float t) {
mPosX += mDirX * t;
mPosY += mDirY * t;
if (mPosX < -mAvailableX) {
mDirX = Math.abs(mDirX);
} else if (mPosX > mAvailableX) {
mDirX = -Math.abs(mDirX);
}
if (mPosY < -mAvailableY) {
mDirY = Math.abs(mDirY);
} else if (mPosY > mAvailableY) {
mDirY = -Math.abs(mDirY);
}
}
/**
* Returns Model/View/Projection transform for the patch.
*/
public float[] getTransform(@NonNull float[] vpMatrix) {
Matrix.setIdentityM(mMatrix, 0);
mMatrix[12] = mPosX;
mMatrix[13] = mPosY;
Matrix.multiplyMM(mMatrix, 0, vpMatrix, 0, mMatrix, 0);
return mMatrix;
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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.util.List;
import javax.microedition.khronos.opengles.GL;
import android.annotation.NonNull;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;
/**
* Base class for all OpenGL based tests that use RenderPatch as a base.
*/
public abstract class RenderPatchOpenGLTest extends OpenGLTest {
private final float[] COLOR = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
private final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;"
+ "attribute vec4 vPosition;"
+ "attribute vec2 vTexture;"
+ "varying vec2 vTex;"
+ "void main() {"
+ " vTex = vTexture;"
+ " gl_Position = uMVPMatrix * vPosition;"
+ "}";
private final String FRAGMENT_SHADER =
"precision mediump float;"
+ "uniform sampler2D uTexture;"
+ "uniform vec4 uColor;"
+ "varying vec2 vTex;"
+ "void main() {"
+ " vec4 color = texture2D(uTexture, vTex);"
+ " gl_FragColor = uColor * color;"
+ "}";
private List<RenderPatchAnimation> mRenderPatches;
private int mProgram = -1;
private int mMVPMatrixHandle;
private int mTextureHandle;
private int mPositionHandle;
private int mColorHandle;
private int mTextureCoordHandle;
private final float[] mVPMatrix = new float[16];
public RenderPatchOpenGLTest(@NonNull GamePerformanceActivity activity) {
super(activity);
}
protected void setRenderPatches(@NonNull List<RenderPatchAnimation> renderPatches) {
mRenderPatches = renderPatches;
}
private void ensureInited() {
if (mProgram >= 0) {
return;
}
mProgram = OpenGLUtils.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
// get handle to fragment shader's uColor member
GLES20.glUseProgram(mProgram);
OpenGLUtils.checkGlError("useProgram");
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
OpenGLUtils.checkGlError("get uMVPMatrix");
mTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture");
OpenGLUtils.checkGlError("uTexture");
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
OpenGLUtils.checkGlError("vPosition");
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "vTexture");
OpenGLUtils.checkGlError("vTexture");
mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
OpenGLUtils.checkGlError("uColor");
mTextureHandle = OpenGLUtils.createTexture(getContext(), R.drawable.logo);
final float[] projectionMatrix = new float[16];
final float[] viewMatrix = new float[16];
final float ratio = getView().getRenderRatio();
Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, -1, 1);
Matrix.setLookAtM(viewMatrix, 0, 0, 0, -0.5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
Matrix.multiplyMM(mVPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}
/**
* Returns global color for patch.
*/
public float[] getColor() {
return COLOR;
}
/**
* Extra setup for particular tests.
*/
public void onBeforeDraw(GL gl) {
}
@Override
public void draw(GL gl) {
ensureInited();
GLES20.glUseProgram(mProgram);
OpenGLUtils.checkGlError("useProgram");
GLES20.glDisable(GLES20.GL_BLEND);
OpenGLUtils.checkGlError("disableBlend");
GLES20.glEnableVertexAttribArray(mPositionHandle);
OpenGLUtils.checkGlError("enableVertexAttributes");
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
OpenGLUtils.checkGlError("enableTexturesAttributes");
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureHandle);
OpenGLUtils.checkGlError("setTexture");
GLES20.glUniform4fv(mColorHandle, 1, getColor(), 0);
OpenGLUtils.checkGlError("setColor");
onBeforeDraw(gl);
for (final RenderPatchAnimation renderPatchAnimation : mRenderPatches) {
renderPatchAnimation.update(0.01f);
GLES20.glUniformMatrix4fv(mMVPMatrixHandle,
1,
false,
renderPatchAnimation.getTransform(mVPMatrix),
0);
OpenGLUtils.checkGlError("setTransform");
GLES20.glVertexAttribPointer(
mPositionHandle,
RenderPatch.VERTEX_COORD_COUNT,
GLES20.GL_FLOAT,
false /* normalized */,
RenderPatch.VERTEX_STRIDE,
renderPatchAnimation.getRenderPatch().getVertexBuffer());
OpenGLUtils.checkGlError("setVertexAttribute");
GLES20.glVertexAttribPointer(
mTextureCoordHandle,
RenderPatch.TEXTURE_COORD_COUNT,
GLES20.GL_FLOAT,
false /* normalized */,
RenderPatch.TEXTURE_STRIDE,
renderPatchAnimation.getRenderPatch().getTextureBuffer());
OpenGLUtils.checkGlError("setTextureAttribute");
// Draw the patch.
final int indicesCount =
renderPatchAnimation.getRenderPatch().getIndexBuffer().capacity() /
RenderPatch.SHORT_SIZE;
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
indicesCount,
GLES20.GL_UNSIGNED_SHORT,
renderPatchAnimation.getRenderPatch().getIndexBuffer());
OpenGLUtils.checkGlError("drawPatch");
}
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.util.ArrayList;
import java.util.List;
import android.annotation.NonNull;
/**
* Test that measures maximum amount of triangles can be rasterized keeping FPS close to the device
* refresh rate. It is has very few devices call and each call contains big amount of triangles.
* Total filling area is around one screen.
*/
public class TriangleCountOpenGLTest extends RenderPatchOpenGLTest {
// Based on index buffer of short values.
private final static int MAX_TRIANGLES_IN_PATCH = 32000;
public TriangleCountOpenGLTest(@NonNull GamePerformanceActivity activity) {
super(activity);
}
@Override
public String getName() {
return "triangle_count";
}
@Override
public String getUnitName() {
return "ktriangles";
}
@Override
public double getUnitScale() {
return 2.0;
}
@Override
public void initUnits(double trianlgeCountD) {
final int triangleCount = (int)Math.round(trianlgeCountD * 1000.0);
final List<RenderPatchAnimation> renderPatches = new ArrayList<>();
final int patchCount =
(triangleCount + MAX_TRIANGLES_IN_PATCH - 1) / MAX_TRIANGLES_IN_PATCH;
final int patchTriangleCount = triangleCount / patchCount;
for (int i = 0; i < patchCount; ++i) {
final RenderPatch renderPatch = new RenderPatch(patchTriangleCount,
0.5f /* dimension */,
RenderPatch.TESSELLATION_TO_CENTER);
renderPatches.add(new RenderPatchAnimation(renderPatch, getView().getRenderRatio()));
}
setRenderPatches(renderPatches);
}
}