add OpelGl performance test as a separate package
Currently runs two types of tests
1. rendering performance comparison with VBO vs without VBO
This can also work as a stress test due to the amount of triangles used.
2. rendering performance comparison with varying numbers of index buffers
This is for bug 5660942
Change-Id: I8a7d05cf0bfaa7063302efdd7fb24519d3a3c5bf
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 3ccf169..c0be204 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -48,6 +48,7 @@
CtsMediaTestCases \
CtsNdefTestCases \
CtsNetTestCases \
+ CtsOpenGlPerfTestCases \
CtsOsTestCases \
CtsPermissionTestCases \
CtsPermission2TestCases \
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
new file mode 100644
index 0000000..0663c73
--- /dev/null
+++ b/tests/tests/openglperf/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsOpenGlPerfTestCases
+
+# uncomment when dalvik.annotation.Test* are removed or part of SDK
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
new file mode 100644
index 0000000..95a7cd1
--- /dev/null
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.openglperf"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+ <instrumentation
+ android:targetPackage="com.android.cts.openglperf"
+ android:name="android.test.InstrumentationTestRunner" />
+ <application
+ android:label="@string/app_name" >
+ <uses-library android:name="android.test.runner" />
+ <activity
+ android:name="android.openglperf.cts.GlPlanetsActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tests/tests/openglperf/assets/world_512_512.jpg b/tests/tests/openglperf/assets/world_512_512.jpg
new file mode 100644
index 0000000..b92bfae
--- /dev/null
+++ b/tests/tests/openglperf/assets/world_512_512.jpg
Binary files differ
diff --git a/tests/tests/openglperf/res/values/strings.xml b/tests/tests/openglperf/res/values/strings.xml
new file mode 100644
index 0000000..2cdba7f
--- /dev/null
+++ b/tests/tests/openglperf/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<resources>
+ <string name="app_name">OpenGlPerfTest</string>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
new file mode 100644
index 0000000..5a8c0a86
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlPlanetsActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity which runs GL test and measure FPS with given parameters passed from
+ * Intent. For the meaning of parameters, check {@PlanetsRenderingParam}
+ */
+public class GlPlanetsActivity extends Activity implements
+ RenderCompletionListener {
+ public static final String INTENT_EXTRA_NUM_PLANETS = "numPlanets";
+ public static final String INTENT_EXTRA_USE_VBO_VERTICES = "useVboVertices";
+ public static final String INTENT_EXTRA_USE_VBO_INDICES = "useVboIndiices";
+ public static final String INTENT_EXTRA_NUM_FRAMES = "numFrames";
+ public static final String INTENT_EXTRA_INDICES_PER_VERTEX = "numIndicesPerVertex";
+
+ public static final String INTENT_RESULT_FPS = "fps";
+ public static final String INTENT_RESULT_NUM_TRIANGLES = "numTrigngles";
+
+ private final Semaphore mSem = new Semaphore(0);
+ private float mFps;
+ private int mNumTriangles;
+
+ private PlanetsSurfaceView mView;
+
+ public boolean waitForGlPlanetsCompletionWithTimeout(long timeoutInSecs)
+ throws InterruptedException {
+ return mSem.tryAcquire(timeoutInSecs, TimeUnit.SECONDS);
+ }
+
+ public float getFps() {
+ return mFps;
+ }
+
+ public int getNumTriangles() {
+ return mNumTriangles;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ Intent intent = getIntent();
+ PlanetsRenderingParam param = new PlanetsRenderingParam();
+ param.mNumPlanets = intent.getIntExtra(INTENT_EXTRA_NUM_PLANETS, param.mNumPlanets);
+ param.mUseVboForVertices = intent.getBooleanExtra(INTENT_EXTRA_USE_VBO_VERTICES,
+ param.mUseVboForVertices);
+ param.mUseVboForIndices = intent.getBooleanExtra(INTENT_EXTRA_USE_VBO_INDICES,
+ param.mUseVboForIndices);
+ param.mNumFrames = intent.getIntExtra(INTENT_EXTRA_NUM_FRAMES, param.mNumFrames);
+ param.mNumIndicesPerVertex = intent.getIntExtra(INTENT_EXTRA_INDICES_PER_VERTEX,
+ param.mNumIndicesPerVertex);
+ mView = new PlanetsSurfaceView(this, param, this);
+ setContentView(mView);
+ }
+
+ @Override
+ public void onRenderCompletion(float fps, int numTriangles) {
+ mFps = fps;
+ mNumTriangles = numTriangles;
+ mSem.release();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.onPause();
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
new file mode 100644
index 0000000..afd435c
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlVboPerfTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+public class GlVboPerfTest extends
+ ActivityInstrumentationTestCase2<GlPlanetsActivity> {
+ private static final String TAG = "GlVboPerfTest";
+ private static final int NUM_FRAMES_TO_RENDER = 100;
+ private static final long RENDERING_TIMEOUT = 5 * 60;
+ // 10% of fps_no_vbo is allowed to compensate variations in measurement
+ private static final float FPS_COMPARISON_MARGIN = 0.1f;
+ // the worst case should be above 70% of the best case
+ private static final float FPS_MIN_MAX_COMPARISON_PERCENTILE = 0.7f;
+
+ private float mFps;
+ private int mNumTriangles;
+
+ public GlVboPerfTest() {
+ super(GlPlanetsActivity.class);
+ }
+
+ public void testVboWithVaryingIndexBufferNumbers() throws Exception {
+ final int[] numIndexBuffers = {1, 10, 100, 200, 400}; // per vertex buffer
+ float[] fpsVbo = new float[numIndexBuffers.length];
+ float[] fpsNonVbo = new float[numIndexBuffers.length];
+
+ for (int i = 0; i < numIndexBuffers.length; i++) {
+ runRendering(0, true, true, numIndexBuffers[i]);
+ fpsVbo[i] = mFps;
+ runRendering(0, true, false, numIndexBuffers[i]);
+ fpsNonVbo[i] = mFps;
+ }
+ StringBuilder msgIndex = new StringBuilder();
+ StringBuilder msgVbo = new StringBuilder();
+ StringBuilder msgNonVbo = new StringBuilder();
+ msgIndex.append("index buffer ");
+ msgVbo.append("Vbo ");
+ msgNonVbo.append("Non-Vbo ");
+ for (int i = 0; i < numIndexBuffers.length; i++) {
+ msgIndex.append(numIndexBuffers[i]).append(" ");
+ msgVbo.append(fpsVbo[i]).append(" ");
+ msgNonVbo.append(fpsNonVbo[i]).append(" ");
+ }
+ Log.i(TAG, msgIndex.toString());
+ Log.i(TAG, msgVbo.toString());
+ Log.i(TAG, msgNonVbo.toString());
+
+ float[] minMaxVbo = findMinMax(fpsVbo);
+ float[] minMaxNonVbo = findMinMax(fpsNonVbo);
+
+ float delta = minMaxVbo[1] - (1f - FPS_COMPARISON_MARGIN)
+ * minMaxNonVbo[1];
+ assertTrue("VBO performance worse than non-VBO " + msgVbo + msgNonVbo, delta > 0f);
+ assertTrue(
+ "Too much FPS drop for VBO case " + msgVbo,
+ minMaxVbo[0] > (FPS_MIN_MAX_COMPARISON_PERCENTILE * minMaxVbo[1]));
+ assertTrue(
+ "Too much FPS drop for No VBO case " + msgNonVbo,
+ minMaxNonVbo[0] > (FPS_MIN_MAX_COMPARISON_PERCENTILE * minMaxNonVbo[1]));
+ }
+
+ public void testVboVsNonVboPerfGeometry0() throws Exception {
+ doRunVboVsNonVboPerfTest(0);
+ }
+
+ public void testVboVsNonVboPerfGeometry1() throws Exception {
+ doRunVboVsNonVboPerfTest(4);
+ }
+
+ private void runRendering(int numPlanets, boolean useVboVertex, boolean useVboIndex,
+ int indicesPerVertex) throws Exception {
+ Intent intent = new Intent();
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_FRAMES,
+ NUM_FRAMES_TO_RENDER);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_NUM_PLANETS, numPlanets);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_VERTICES, useVboVertex);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_USE_VBO_INDICES, useVboIndex);
+ intent.putExtra(GlPlanetsActivity.INTENT_EXTRA_INDICES_PER_VERTEX, indicesPerVertex);
+
+ setActivityIntent(intent);
+ final GlPlanetsActivity activity = getActivity();
+ boolean waitResult = activity
+ .waitForGlPlanetsCompletionWithTimeout(RENDERING_TIMEOUT);
+ assertTrue("timeout while waiting for rendering completion", waitResult);
+
+ mFps = activity.getFps();
+ mNumTriangles = activity.getNumTriangles();
+
+ cleanUpActivity();
+ }
+
+ private void cleanUpActivity() throws Exception {
+ // finish the current activity and do clean-up so that a new activity
+ // can be launched in the same test run
+ super.tearDown();
+ super.setUp();
+ // wait until clean-up / set-up finishes
+ getInstrumentation().waitForIdleSync();
+ }
+
+ private void doRunVboVsNonVboPerfTest(int numPlanets) throws Exception {
+ runRendering(numPlanets, true, true, 1); // VBO
+ int numTrianglesVbo = mNumTriangles;
+ float fpsVbo = mFps;
+ runRendering(numPlanets, false, false, 1); // non-VBO
+
+ assertTrue("Number of triangles mismatch",
+ numTrianglesVbo == mNumTriangles);
+
+ // Margin amount of error is allowed due to measuring irregularity
+ float delta = fpsVbo - (1f - FPS_COMPARISON_MARGIN) * mFps;
+ StringBuilder testMsg = new StringBuilder();
+ testMsg.append("VBO performance worse than non-VBO ").append(fpsVbo).append(" ");
+ testMsg.append(mFps);
+ assertTrue(testMsg.toString(), delta > 0f);
+ }
+
+ private float[] findMinMax(float[] data) {
+ float min = data[0];
+ float max = data[0];
+
+ for (int i = 1; i < data.length; i++) {
+ if (data[i] > max) max = data[i];
+ if (data[i] < min) min = data[i];
+ }
+ float[] result = {min, max};
+ return result;
+ }
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
new file mode 100644
index 0000000..59a7782
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderer.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+
+public class PlanetsRenderer implements GLSurfaceView.Renderer {
+
+ private static final String TAG = "PlanetsRenderer";
+ // texture is from
+ // http://en.wikipedia.org/wiki/File:Mercator_projection_SW.jpg
+ private static final String TEXTURE_FILE = "world_512_512.jpg";
+
+ private final Context mContext;
+ private final PlanetsRenderingParam mParam;
+ private final RenderCompletionListener mListener;
+
+ private final Sphere[] mSpheres;
+ private final int mNumSpheres;
+ private final int mNumIndices;
+ private final int mVboVertices[];
+ private final int mVboIndices[];
+
+ // configurations for sun and planets
+ private static final int SPHERE_SLICES = 180;
+ private static final float RADIUS_SUN = 0.4f;
+ private static final float RADIUS_PLANET = 0.08f;
+ private static final float RADIUS_ORBIT = 0.9f;
+
+ private int mWidth;
+ private int mHeight;
+
+ private int mFrameCount = 0;
+ private static final int FPS_DISPLAY_INTERVAL = 50;
+ private long mLastFPSTime;
+ // for total FPS measurement
+ private long mRenderingStartTime;
+ private long mMeasurementStartTime;
+
+ private int mProgram; // shader program
+ private int mMVPMatrixHandle;
+ private float[] mMVPMatrix = new float[16];
+ private float[] mMMatrix = new float[16];
+ private float[] mVMatrix = new float[16];
+ private float[] mProjMatrix = new float[16];
+
+ private int mOffsetHandle;
+ private static final float[] mDefaultOffset = { 0f, 0f, 0f, 1f };
+ private int mPositionHandle;
+ private int mTexCoord0Handle;
+ private int mTextureHandle;
+ private int mTextureId;
+
+ /**
+ * @param numSlices
+ * complexity of sphere used. A sphere will have (numSlices + 1)
+ * x (numSlices x 1) much of vertices
+ * @param useVbo
+ * whether to use Vertex Buffer Object in rendering or not
+ * @param framesToGo
+ * number of frames to render before calling completion to
+ * listener
+ * @param listener
+ */
+ public PlanetsRenderer(Context context, PlanetsRenderingParam param,
+ RenderCompletionListener listener) {
+ resetTimer();
+ mContext = context;
+ mParam = param;
+ mNumSpheres = mParam.mNumPlanets + 1; // 1 for sun
+ mNumIndices = mNumSpheres * mParam.mNumIndicesPerVertex;
+ mSpheres = new Sphere[mNumSpheres];
+
+ printParams();
+
+ // for big model, this construction phase takes time...
+ mSpheres[0] = new Sphere(SPHERE_SLICES, 0f, 0f, 0f, RADIUS_SUN,
+ mParam.mNumIndicesPerVertex);
+ for (int i = 1; i < mNumSpheres; i++) {
+ mSpheres[i] = new Sphere(SPHERE_SLICES,
+ RADIUS_ORBIT * (float) Math.sin(((float) i) / (mNumSpheres - 1) * 2 * Math.PI),
+ RADIUS_ORBIT * (float) Math.cos(((float) i) / (mNumSpheres - 1) * 2 * Math.PI),
+ 0f, RADIUS_PLANET, mParam.mNumIndicesPerVertex);
+ }
+ mVboVertices = new int[mNumSpheres];
+ mVboIndices = new int[mNumIndices];
+ mListener = listener;
+ measureTime("construction");
+ }
+
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ mProgram = createProgram(getVertexShader(), getFragmentShader());
+ if (mProgram == 0) {
+ // error, cannot proceed
+ throw new IllegalStateException("createProgram failed");
+ }
+ mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+ mOffsetHandle = GLES20.glGetUniformLocation(mProgram, "uOffset");
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+ mTexCoord0Handle = GLES20.glGetAttribLocation(mProgram, "vTexCoord0");
+ mTextureHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
+
+ // Load the texture
+ mTextureId = createTexture2D();
+ }
+
+ public void onDrawFrame(GL10 glUnused) {
+ long currentTime = System.currentTimeMillis();
+
+ mFrameCount++;
+ if ((mFrameCount % FPS_DISPLAY_INTERVAL == 0) && (mFrameCount != 0)) {
+ float fps = (((float) FPS_DISPLAY_INTERVAL)
+ / ((float) (currentTime - mLastFPSTime)) * 1000.0f);
+ Log.i(TAG, "FPS " + fps);
+ mLastFPSTime = currentTime;
+ }
+
+ if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) {
+ long timePassed = currentTime - mRenderingStartTime;
+ float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f;
+ printGlInfos();
+ printParams();
+ int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3;
+ Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles);
+ if (mListener != null) {
+ mListener.onRenderCompletion(fps, numTriangles);
+ return;
+ }
+ }
+
+ float angle = 0.090f * ((int) (currentTime % 4000L));
+ Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
+ Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
+ Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
+
+ GLES20.glUseProgram(mProgram);
+ GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+
+ // Apply a ModelView Projection transformation
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ GLES20.glUniform4f(mOffsetHandle, mDefaultOffset[0], mDefaultOffset[1],
+ mDefaultOffset[2], mDefaultOffset[3]);
+
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ GLES20.glEnableVertexAttribArray(mTexCoord0Handle);
+
+ // Bind the texture
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
+ // Set the sampler texture unit to 0
+ GLES20.glUniform1i(mTextureHandle, 0);
+
+ for (int i = 0; i < mNumSpheres; i++) {
+ if (mParam.mUseVboForVertices) {
+ // generating VBOs for each sphere is not efficient way for drawing
+ // multiple spheres
+ // But this is done for testing performance with big VBO buffers.
+ // So please do not copy this code as it is.
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboVertices[i]);
+ // Load the vertex position
+ GLES20.glVertexAttribPointer(mPositionHandle, 3,
+ GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(),
+ 0);
+ // Load the texture coordinate
+ GLES20.glVertexAttribPointer(mTexCoord0Handle, 3,
+ GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(),
+ 3 * Sphere.FLOAT_SIZE);
+ } else {
+ // Load the vertex position
+ GLES20.glVertexAttribPointer(mPositionHandle, 3,
+ GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(),
+ mSpheres[i].getVertices());
+ // Load the texture coordinate
+ GLES20.glVertexAttribPointer(mTexCoord0Handle, 3,
+ GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(),
+ mSpheres[i].getVertices().duplicate().position(3));
+ }
+ int[] numIndices = mSpheres[i].getNumIndices();
+ ShortBuffer[] indices = mSpheres[i].getIndices();
+ if (mParam.mUseVboForIndices) {
+ int indexVboBase = i * mParam.mNumIndicesPerVertex;
+ for (int j = 0; j < numIndices.length; j++) {
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER,
+ mVboIndices[indexVboBase + j]);
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES,
+ numIndices[j], GLES20.GL_UNSIGNED_SHORT,
+ 0);
+ }
+ } else {
+ for (int j = 0; j < numIndices.length; j++) {
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES,
+ numIndices[j], GLES20.GL_UNSIGNED_SHORT,
+ indices[j]);
+ }
+ }
+ }
+ }
+
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ GLES20.glViewport(0, 0, width, height);
+ float ratio = (float) width / height;
+ Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
+ Matrix.setLookAtM(mVMatrix, 0, 0, 3, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+
+ createVbo();
+
+ // reset timer to remove delays added for FPS calculation.
+ mLastFPSTime = System.currentTimeMillis();
+ mRenderingStartTime = System.currentTimeMillis();
+ }
+
+ protected final String getVertexShader() {
+ // simple shader with MVP matrix and text coord
+ final String vShaderStr =
+ "uniform mat4 uMVPMatrix; \n"
+ + "uniform vec4 uOffset; \n"
+ + "attribute vec4 vPosition; \n"
+ + "attribute vec2 vTexCoord0; \n"
+ + "varying vec2 vTexCoord; \n"
+ + "void main() \n"
+ + "{ \n"
+ + " gl_Position = uMVPMatrix * (vPosition + uOffset); \n"
+ + " vTexCoord = vTexCoord0; \n"
+ + "} \n";
+ return vShaderStr;
+ }
+
+ protected final String getFragmentShader() {
+ // simple shader with one texture for color
+ final String fShaderStr =
+ "precision mediump float; \n"
+ + "varying vec2 vTexCoord; \n"
+ + "uniform sampler2D sTexture; \n"
+ + "void main() \n"
+ + "{ \n"
+ + " gl_FragColor = texture2D( sTexture, vTexCoord );\n"
+ + "} \n";
+ return fShaderStr;
+ }
+
+ private int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ if (shader != 0) {
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+ }
+
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ if (program != 0) {
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ private int createTexture2D() {
+ // Texture object handle
+ int[] textureId = new int[1];
+
+ InputStream in = null;
+ try {
+ in = mContext.getAssets().open(TEXTURE_FILE);
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+
+ // Generate a texture object
+ GLES20.glGenTextures(1, textureId, 0);
+
+ // Bind the texture object
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);
+ GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+
+ // Set the filtering mode
+ 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);
+
+ } catch (IOException e) {
+ throw new IllegalStateException("Couldn't load texture '" + TEXTURE_FILE
+ + "'", e);
+ } finally {
+ if (in != null)
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+
+ return textureId[0];
+ }
+
+ private void checkGlError(String op) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(TAG, op + ": glError " + error);
+ throw new IllegalStateException(op + ": glError " + error);
+ }
+ }
+
+ private void createVbo() {
+ resetTimer();
+ if (mParam.mUseVboForVertices) {
+ GLES20.glGenBuffers(mNumSpheres, mVboVertices, 0);
+ checkGlError("glGenBuffers Vertex");
+ for (int i = 0; i < mNumSpheres; i++) {
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboVertices[i]);
+ FloatBuffer vertices = mSpheres[i].getVertices();
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertices.limit()
+ * Sphere.FLOAT_SIZE, vertices, GLES20.GL_STATIC_DRAW);
+ checkGlError("glBufferData Vertex");
+ mSpheres[i].releaseVertexBuffer(); // release memory
+ }
+ }
+ if (mParam.mUseVboForIndices) {
+ GLES20.glGenBuffers(mNumIndices, mVboIndices, 0);
+ checkGlError("glGenBuffers Index");
+ for (int i = 0; i < mNumSpheres; i++) {
+ int[] numIndices = mSpheres[i].getNumIndices();
+ ShortBuffer[] indices = mSpheres[i].getIndices();
+ int indexVboBase = i * mParam.mNumIndicesPerVertex;
+ for (int j = 0; j < numIndices.length; j++) {
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER,
+ mVboIndices[indexVboBase + j]);
+ GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER,
+ indices[j].limit() * Sphere.SHORT_SIZE, indices[j],
+ GLES20.GL_STATIC_DRAW);
+ checkGlError("glBufferData Index");
+
+ }
+ mSpheres[i].releaseIndexBuffer(); // release memory
+ }
+ }
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ measureTime("VBO creation");
+ }
+
+ private void resetTimer() {
+ mMeasurementStartTime = System.currentTimeMillis();
+ }
+
+ private void measureTime(String description) {
+ long currentTime = System.currentTimeMillis();
+ float timePassedInSecs = (float) (currentTime - mMeasurementStartTime) / 1000f;
+ Log.i(TAG, description + " time in secs: " + timePassedInSecs);
+ }
+
+ private void printGlInfos() {
+ Log.i(TAG, "Vendor " + GLES20.glGetString(GLES20.GL_VENDOR));
+ Log.i(TAG, "Version " + GLES20.glGetString(GLES20.GL_VERSION));
+ Log.i(TAG, "Renderer " + GLES20.glGetString(GLES20.GL_RENDERER));
+ Log.i(TAG, "Extensions " + GLES20.glGetString(GLES20.GL_EXTENSIONS));
+ }
+ private void printParams() {
+ Log.i(TAG, "UseVboForVertex " + mParam.mUseVboForVertices);
+ Log.i(TAG, "UseVboForIndex " + mParam.mUseVboForIndices);
+ Log.i(TAG, "No Spheres " + mNumSpheres);
+ Log.i(TAG, "No indices buffer per vertex " + mParam.mNumIndicesPerVertex);
+ }
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderingParam.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderingParam.java
new file mode 100644
index 0000000..5e2dfd0
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsRenderingParam.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+public class PlanetsRenderingParam {
+ /** whether to use VBO for vertices data */
+ public boolean mUseVboForVertices = true;
+ /** whether to use VBO for indices data */
+ public boolean mUseVboForIndices = true;
+ /** number of indices buffer per one vertices buffer */
+ public int mNumIndicesPerVertex = 1;
+ /** number of planets to render. There is always a sun */
+ public int mNumPlanets = 0;
+ /** number of frames to render to calculate FPS and finish */
+ public int mNumFrames = 100;
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
new file mode 100644
index 0000000..526f706
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/PlanetsSurfaceView.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+
+class PlanetsSurfaceView extends GLSurfaceView {
+
+ public PlanetsSurfaceView(Context context, PlanetsRenderingParam param,
+ RenderCompletionListener listener) {
+ super(context);
+
+ setEGLContextClientVersion(2);
+ setRenderer(new PlanetsRenderer(context, param, listener));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ setRenderMode(RENDERMODE_WHEN_DIRTY);
+ }
+
+ @Override
+ public void onResume() {
+ setRenderMode(RENDERMODE_CONTINUOUSLY);
+ super.onResume();
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
new file mode 100644
index 0000000..1643087e
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/RenderCompletionListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+public interface RenderCompletionListener {
+ /**
+ * @param fps total frame rate
+ * @param numTriangles Number of triangles in geometric model
+ */
+ void onRenderCompletion(float fps, int numTriangles);
+
+}
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
new file mode 100644
index 0000000..e8876dd
--- /dev/null
+++ b/tests/tests/openglperf/src/android/openglperf/cts/Sphere.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 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.openglperf.cts;
+
+import java.lang.Math;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/*
+ * Class for generating a sphere model for given input params
+ * The generated class will have vertices and indices
+ * Vertices data is composed of vertex coordinates in x, y, z followed by
+ * texture coordinates s, t for each vertex
+ * Indices store vertex indices for the whole sphere.
+ * Formula for generating sphere is originally coming from source code of
+ * OpenGL ES2.0 Programming guide
+ * which is available from http://code.google.com/p/opengles-book-samples/,
+ * but some changes were made to make texture look right.
+ */
+public class Sphere {
+ public static final int FLOAT_SIZE = 4;
+ public static final int SHORT_SIZE = 2;
+
+ private FloatBuffer mVertices;
+ private ShortBuffer[] mIndices;
+ private int[] mNumIndices;
+ private int mTotalIndices;
+
+ /*
+ * @param nSlices how many slice in horizontal direction.
+ * The same slice for vertical direction is applied.
+ * nSlices should be > 1 and should be <= 180
+ * @param x,y,z the origin of the sphere
+ * @param r the radius of the sphere
+ */
+ public Sphere(int nSlices, float x, float y, float z, float r, int indicesPerVertex) {
+
+ int iMax = nSlices + 1;
+ int nVertices = iMax * iMax;
+ if (nVertices > Short.MAX_VALUE) {
+ // this cannot be handled in one vertices / indices pair
+ throw new RuntimeException("nSlices " + nSlices + " too big for vertex");
+ }
+ mTotalIndices = nSlices * nSlices * 6;
+ float angleStepI = ((float) Math.PI / nSlices);
+ float angleStepJ = ((2.0f * (float) Math.PI) / nSlices);
+
+ // 3 vertex coords + 2 texture coords
+ mVertices = ByteBuffer.allocateDirect(nVertices * 5 * FLOAT_SIZE)
+ .order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mIndices = new ShortBuffer[indicesPerVertex];
+ mNumIndices = new int[indicesPerVertex];
+ // first evenly distribute to n-1 buffers, then put remaining ones to the last one.
+ int noIndicesPerBuffer = (mTotalIndices / indicesPerVertex / 6) * 6;
+ for (int i = 0; i < indicesPerVertex - 1; i++) {
+ mNumIndices[i] = noIndicesPerBuffer;
+ }
+ mNumIndices[indicesPerVertex - 1] = mTotalIndices - noIndicesPerBuffer *
+ (indicesPerVertex - 1);
+
+ for (int i = 0; i < indicesPerVertex; i++) {
+ mIndices[i] = ByteBuffer.allocateDirect(mNumIndices[i] * SHORT_SIZE)
+ .order(ByteOrder.nativeOrder()).asShortBuffer();
+ }
+ // calling put for each float took too much CPU time, so put by line instead
+ float[] vLineBuffer = new float[iMax * 5];
+ for (int i = 0; i < iMax; i++) {
+ for (int j = 0; j < iMax; j++) {
+ int vertexBase = j * 5;
+ float sini = (float) Math.sin(angleStepI * i);
+ float sinj = (float) Math.sin(angleStepJ * j);
+ float cosi = (float) Math.cos(angleStepI * i);
+ float cosj = (float) Math.cos(angleStepJ * j);
+ // vertex x,y,z
+ vLineBuffer[vertexBase + 0] = x + r * sini * sinj;
+ vLineBuffer[vertexBase + 1] = y + r * sini * cosj;
+ vLineBuffer[vertexBase + 2] = z + r * cosi;
+ // texture s,t
+ vLineBuffer[vertexBase + 3] = (float) j / (float) nSlices;
+ vLineBuffer[vertexBase + 4] = (1.0f - i) / (float)nSlices;
+ }
+ mVertices.put(vLineBuffer, 0, vLineBuffer.length);
+ }
+
+ short[] indexBuffer = new short[max(mNumIndices)];
+ int index = 0;
+ int bufferNum = 0;
+ for (int i = 0; i < nSlices; i++) {
+ for (int j = 0; j < nSlices; j++) {
+ int i1 = i + 1;
+ int j1 = j + 1;
+ if (index >= mNumIndices[bufferNum]) {
+ // buffer ready for moving to target
+ mIndices[bufferNum].put(indexBuffer, 0, mNumIndices[bufferNum]);
+ // move to the next one
+ index = 0;
+ bufferNum++;
+ }
+ indexBuffer[index++] = (short) (i * iMax + j);
+ indexBuffer[index++] = (short) (i1 * iMax + j);
+ indexBuffer[index++] = (short) (i1 * iMax + j1);
+ indexBuffer[index++] = (short) (i * iMax + j);
+ indexBuffer[index++] = (short) (i1 * iMax + j1);
+ indexBuffer[index++] = (short) (i * iMax + j1);
+ }
+ }
+ mIndices[bufferNum].put(indexBuffer, 0, mNumIndices[bufferNum]);
+
+ mVertices.position(0);
+ for (int i = 0; i < indicesPerVertex; i++) {
+ mIndices[i].position(0);
+ }
+ }
+
+ public FloatBuffer getVertices() {
+ return mVertices;
+ }
+
+ public int getVeticesStride() {
+ return 5*FLOAT_SIZE;
+ }
+
+ public ShortBuffer[] getIndices() {
+ return mIndices;
+ }
+
+ public int[] getNumIndices() {
+ return mNumIndices;
+ }
+
+ public int getTotalIndices() {
+ return mTotalIndices;
+ }
+ /** once VBO buffer is generated, model data can be dropped */
+ public void releaseVertexBuffer() {
+ mVertices = null;
+ }
+
+ public void releaseIndexBuffer() {
+ mVertices = null;
+ }
+
+ private int max(int[] array) {
+ int max = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > max) max = array[i];
+ }
+ return max;
+ }
+}