| /* |
| * 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 RenderingWatchDog mWatchDog; |
| |
| 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, RenderingWatchDog watchDog) { |
| resetTimer(); |
| mContext = context; |
| mParam = param; |
| mWatchDog = watchDog; |
| 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) { |
| mWatchDog.reset(); |
| long currentTime = System.currentTimeMillis(); |
| |
| mFrameCount++; |
| if ((mFrameCount % FPS_DISPLAY_INTERVAL == 0) && (mFrameCount != 0)) { |
| float fps = (((float) FPS_DISPLAY_INTERVAL) |
| / ((float) (currentTime - mLastFPSTime)) * 1000.0f); |
| // FPS is not correct if activity is paused/resumed. |
| 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]); |
| checkGlError("glBindBuffer Vertex"); |
| FloatBuffer vertices = mSpheres[i].getVertices(); |
| GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertices.limit() |
| * Sphere.FLOAT_SIZE, vertices, GLES20.GL_STATIC_DRAW); |
| checkGlError("glBufferData Vertex"); |
| } |
| } |
| 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"); |
| |
| } |
| } |
| } |
| 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); |
| } |
| } |