diff options
author | khmel@google.com <khmel@google.com> | 2019-06-21 08:49:03 -0700 |
---|---|---|
committer | Yury Khmel <khmel@google.com> | 2019-06-24 15:26:47 +0000 |
commit | e41ec354bb0a6802fa0db4695ce443573f767b4c (patch) | |
tree | 8436aabf99db14e56a1bb3a783793104e174870f /tests/GamePerformance/src | |
parent | 652e05f0ff18f321c63b7ab6731338af2fae9051 (diff) |
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)
Diffstat (limited to 'tests/GamePerformance/src')
15 files changed, 1427 insertions, 24 deletions
diff --git a/tests/GamePerformance/src/android/gameperformance/BaseTest.java b/tests/GamePerformance/src/android/gameperformance/BaseTest.java new file mode 100644 index 000000000000..b0640b444914 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/BaseTest.java @@ -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; + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java new file mode 100644 index 000000000000..fa6f03bf88cf --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CPULoadThread.java @@ -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; + } + } + } + } +} diff --git a/tests/GamePerformance/src/android/gameperformance/ControlsTest.java b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java new file mode 100644 index 000000000000..6c36ddcc620d --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/ControlsTest.java @@ -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(); + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CustomControlView.java b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java new file mode 100644 index 000000000000..219085a83b2c --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CustomControlView.java @@ -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)); + } + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java index 2b37280ae9b5..08697ae95376 100644 --- a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java +++ b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java @@ -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; + } } diff --git a/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java new file mode 100644 index 000000000000..df2ae5cf670a --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/DeviceCallsOpenGLTest.java @@ -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); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java new file mode 100644 index 000000000000..9b2619372d16 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/FillRateOpenGLTest.java @@ -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"); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java index b0e6196b53d7..dc745f17e698 100644 --- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java @@ -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"); } diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java index e5de7d75886e..d6e2861c03a7 100644 --- a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java @@ -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; + } } diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java new file mode 100644 index 000000000000..1d3f95cdd5b8 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/OpenGLTest.java @@ -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); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java new file mode 100644 index 000000000000..4f98c52b4b39 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/OpenGLUtils.java @@ -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; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatch.java b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java new file mode 100644 index 000000000000..2e69a61475db --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatch.java @@ -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; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java new file mode 100644 index 000000000000..7dcdb00e1014 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchAnimation.java @@ -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; + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java new file mode 100644 index 000000000000..7492cc034234 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/RenderPatchOpenGLTest.java @@ -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); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java new file mode 100644 index 000000000000..593f37bf9128 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/TriangleCountOpenGLTest.java @@ -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); + } +}
\ No newline at end of file |