1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.openglperf.cts; 18 19 import com.android.compatibility.common.util.WatchDog; 20 21 import android.content.Context; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.opengl.GLES20; 25 import android.opengl.GLUtils; 26 import android.opengl.Matrix; 27 import android.util.Log; 28 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.lang.System; 32 import java.nio.FloatBuffer; 33 import java.nio.ShortBuffer; 34 35 import javax.microedition.khronos.egl.EGLConfig; 36 import javax.microedition.khronos.opengles.GL10; 37 38 /** 39 * OpenGl renderer rendering given number of planets with different GL configuration. 40 */ 41 public class PlanetsRenderer implements GLSurfaceViewCustom.Renderer { 42 43 private static final String TAG = "PlanetsRenderer"; 44 // texture is from 45 // http://en.wikipedia.org/wiki/File:Mercator_projection_SW.jpg 46 private static final String TEXTURE_FILE = "world_512_512.jpg"; 47 private static final long EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS = 100 * 1000 * 1000 * 1000L; 48 49 private final Context mContext; 50 private final PlanetsRenderingParam mParam; 51 private final RenderCompletionListener mListener; 52 private final WatchDog mWatchDog; 53 54 private final Sphere[] mSpheres; 55 private final int mNumSpheres; 56 private final int mNumIndices; 57 private final int mVboVertices[]; 58 private final int mVboIndices[]; 59 60 // configurations for sun and planets 61 private static final int SPHERE_SLICES = 180; 62 private static final float RADIUS_SUN = 0.4f; 63 private static final float RADIUS_PLANET = 0.08f; 64 private static final float RADIUS_ORBIT = 0.9f; 65 66 private int mWidth; 67 private int mHeight; 68 69 private int mFrameCount = 0; 70 private static final int FPS_DISPLAY_INTERVAL = 50; 71 private long mLastFPSTime; 72 private long mLastRenderingTime; 73 // for total FPS measurement 74 private long mRenderingStartTime; 75 private long mMeasurementStartTime; 76 private int[] mFrameInterval = null; 77 78 private int mProgram; // shader program 79 private int mMVPMatrixHandle; 80 private float[] mMVPMatrix = new float[16]; 81 private float[] mMMatrix = new float[16]; 82 private float[] mVMatrix = new float[16]; 83 private float[] mProjMatrix = new float[16]; 84 85 private int mOffsetHandle; 86 private static final float[] mDefaultOffset = { 0f, 0f, 0f, 1f }; 87 private int mPositionHandle; 88 private int mTexCoord0Handle; 89 private int mTextureHandle; 90 private int mTextureId; 91 private String mRendererName; 92 93 /** 94 * @param numSlices 95 * complexity of sphere used. A sphere will have (numSlices + 1) 96 * x (numSlices x 1) much of vertices 97 * @param useVbo 98 * whether to use Vertex Buffer Object in rendering or not 99 * @param framesToGo 100 * number of frames to render before calling completion to 101 * listener 102 * @param listener 103 */ PlanetsRenderer(Context context, PlanetsRenderingParam param, RenderCompletionListener listener, WatchDog watchDog)104 public PlanetsRenderer(Context context, PlanetsRenderingParam param, 105 RenderCompletionListener listener, WatchDog watchDog) { 106 resetTimer(); 107 mContext = context; 108 mParam = param; 109 mWatchDog = watchDog; 110 mNumSpheres = mParam.mNumPlanets + 1; // 1 for sun 111 mNumIndices = mNumSpheres * mParam.mNumIndicesPerVertex; 112 mSpheres = new Sphere[mNumSpheres]; 113 114 if (mParam.mNumFrames > 0) { 115 mFrameInterval = new int[mParam.mNumFrames]; 116 } 117 printParams(); 118 119 // for big model, this construction phase takes time... 120 mSpheres[0] = new Sphere(SPHERE_SLICES, 0f, 0f, 0f, RADIUS_SUN, 121 mParam.mNumIndicesPerVertex); 122 for (int i = 1; i < mNumSpheres; i++) { 123 mSpheres[i] = new Sphere(SPHERE_SLICES, 124 RADIUS_ORBIT * (float) Math.sin(((float) i) / (mNumSpheres - 1) * 2 * Math.PI), 125 RADIUS_ORBIT * (float) Math.cos(((float) i) / (mNumSpheres - 1) * 2 * Math.PI), 126 0f, RADIUS_PLANET, mParam.mNumIndicesPerVertex); 127 } 128 mVboVertices = new int[mNumSpheres]; 129 mVboIndices = new int[mNumIndices]; 130 mListener = listener; 131 measureTime("construction"); 132 } 133 134 @Override onSurfaceCreated(GL10 glUnused, EGLConfig config)135 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { 136 mProgram = createProgram(getVertexShader(), getFragmentShader()); 137 if (mProgram == 0) { 138 // error, cannot proceed 139 throw new IllegalStateException("createProgram failed"); 140 } 141 mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 142 mOffsetHandle = GLES20.glGetUniformLocation(mProgram, "uOffset"); 143 mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); 144 mTexCoord0Handle = GLES20.glGetAttribLocation(mProgram, "vTexCoord0"); 145 mTextureHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); 146 mRendererName = GLES20.glGetString(GLES20.GL_RENDERER); 147 148 // Load the texture 149 mTextureId = createTexture2D(); 150 } 151 152 @Override onDrawFrame(GL10 glUnused)153 public void onDrawFrame(GL10 glUnused) { 154 mWatchDog.reset(); 155 long currentTime = System.currentTimeMillis(); 156 mFrameCount++; 157 mLastRenderingTime = currentTime; 158 159 float angle = 0.090f * ((int) (currentTime % 4000L)); 160 Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f); 161 Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0); 162 Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0); 163 164 GLES20.glUseProgram(mProgram); 165 GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 166 GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); 167 168 // Apply a ModelView Projection transformation 169 GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0); 170 GLES20.glUniform4f(mOffsetHandle, mDefaultOffset[0], mDefaultOffset[1], 171 mDefaultOffset[2], mDefaultOffset[3]); 172 173 GLES20.glEnableVertexAttribArray(mPositionHandle); 174 GLES20.glEnableVertexAttribArray(mTexCoord0Handle); 175 176 // Bind the texture 177 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 178 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId); 179 // Set the sampler texture unit to 0 180 GLES20.glUniform1i(mTextureHandle, 0); 181 182 for (int i = 0; i < mNumSpheres; i++) { 183 if (mParam.mUseVboForVertices) { 184 // generating VBOs for each sphere is not efficient way for drawing 185 // multiple spheres 186 // But this is done for testing performance with big VBO buffers. 187 // So please do not copy this code as it is. 188 GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboVertices[i]); 189 // Load the vertex position 190 GLES20.glVertexAttribPointer(mPositionHandle, 3, 191 GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(), 192 0); 193 // Load the texture coordinate 194 GLES20.glVertexAttribPointer(mTexCoord0Handle, 3, 195 GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(), 196 3 * Sphere.FLOAT_SIZE); 197 } else { 198 // Load the vertex position 199 GLES20.glVertexAttribPointer(mPositionHandle, 3, 200 GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(), 201 mSpheres[i].getVertices()); 202 // Load the texture coordinate 203 GLES20.glVertexAttribPointer(mTexCoord0Handle, 3, 204 GLES20.GL_FLOAT, false, mSpheres[i].getVeticesStride(), 205 mSpheres[i].getVertices().duplicate().position(3)); 206 } 207 int[] numIndices = mSpheres[i].getNumIndices(); 208 ShortBuffer[] indices = mSpheres[i].getIndices(); 209 if (mParam.mUseVboForIndices) { 210 int indexVboBase = i * mParam.mNumIndicesPerVertex; 211 for (int j = 0; j < numIndices.length; j++) { 212 GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 213 mVboIndices[indexVboBase + j]); 214 GLES20.glDrawElements(GLES20.GL_TRIANGLES, 215 numIndices[j], GLES20.GL_UNSIGNED_SHORT, 216 0); 217 } 218 } else { 219 for (int j = 0; j < numIndices.length; j++) { 220 GLES20.glDrawElements(GLES20.GL_TRIANGLES, 221 numIndices[j], GLES20.GL_UNSIGNED_SHORT, 222 indices[j]); 223 } 224 } 225 } 226 } 227 228 @Override onEglSwapBuffers()229 public void onEglSwapBuffers() { 230 if (!OpenGlPerfNative.waitForEglCompletion(EGL_SWAP_BUFFERS_WAIT_TIME_IN_NS)) { 231 Log.w(TAG, "time-out or error while waiting for eglSwapBuffers completion"); 232 } 233 long currentTime = System.currentTimeMillis(); 234 if (mFrameCount == 0) { 235 mRenderingStartTime = currentTime; 236 } 237 if (mFrameCount < mParam.mNumFrames) { 238 mFrameInterval[mFrameCount] = (int)(currentTime - mLastRenderingTime); 239 } 240 241 if ((mFrameCount == mParam.mNumFrames) && (mParam.mNumFrames > 0)) { 242 long timePassed = currentTime - mRenderingStartTime; 243 float fps = ((float) mParam.mNumFrames) / ((float) timePassed) * 1000.0f; 244 printGlInfos(); 245 printParams(); 246 int numTriangles = mNumSpheres * mSpheres[0].getTotalIndices() / 3; 247 Log.i(TAG, "Final FPS " + fps + " Num triangles " + numTriangles + " start time " + 248 mRenderingStartTime + " finish time " + currentTime); 249 if (mListener != null) { 250 mListener.onRenderCompletion(fps, numTriangles, mFrameInterval, mRendererName); 251 mFrameCount++; // to prevent entering here again 252 return; 253 } 254 } 255 } 256 257 @Override onSurfaceChanged(GL10 glUnused, int width, int height)258 public void onSurfaceChanged(GL10 glUnused, int width, int height) { 259 mWidth = width; 260 mHeight = height; 261 GLES20.glViewport(0, 0, width, height); 262 float ratio = (float) width / height; 263 Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7); 264 Matrix.setLookAtM(mVMatrix, 0, 0, 3, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f); 265 266 createVbo(); 267 268 // reset timer to remove delays added for FPS calculation. 269 mLastFPSTime = System.currentTimeMillis(); 270 mRenderingStartTime = System.currentTimeMillis(); 271 } 272 getVertexShader()273 protected final String getVertexShader() { 274 // simple shader with MVP matrix and text coord 275 final String vShaderStr = 276 "uniform mat4 uMVPMatrix; \n" 277 + "uniform vec4 uOffset; \n" 278 + "attribute vec4 vPosition; \n" 279 + "attribute vec2 vTexCoord0; \n" 280 + "varying vec2 vTexCoord; \n" 281 + "void main() \n" 282 + "{ \n" 283 + " gl_Position = uMVPMatrix * (vPosition + uOffset); \n" 284 + " vTexCoord = vTexCoord0; \n" 285 + "} \n"; 286 return vShaderStr; 287 } 288 getFragmentShader()289 protected final String getFragmentShader() { 290 // simple shader with one texture for color 291 final String fShaderStr = 292 "precision mediump float; \n" 293 + "varying vec2 vTexCoord; \n" 294 + "uniform sampler2D sTexture; \n" 295 + "void main() \n" 296 + "{ \n" 297 + " gl_FragColor = texture2D( sTexture, vTexCoord );\n" 298 + "} \n"; 299 return fShaderStr; 300 } 301 loadShader(int shaderType, String source)302 private int loadShader(int shaderType, String source) { 303 int shader = GLES20.glCreateShader(shaderType); 304 if (shader != 0) { 305 GLES20.glShaderSource(shader, source); 306 GLES20.glCompileShader(shader); 307 int[] compiled = new int[1]; 308 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 309 if (compiled[0] == 0) { 310 Log.e(TAG, "Could not compile shader " + shaderType + ":"); 311 Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 312 GLES20.glDeleteShader(shader); 313 shader = 0; 314 } 315 } 316 return shader; 317 } 318 createProgram(String vertexSource, String fragmentSource)319 private int createProgram(String vertexSource, String fragmentSource) { 320 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 321 if (vertexShader == 0) { 322 return 0; 323 } 324 325 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 326 if (pixelShader == 0) { 327 return 0; 328 } 329 330 int program = GLES20.glCreateProgram(); 331 if (program != 0) { 332 GLES20.glAttachShader(program, vertexShader); 333 checkGlError("glAttachShader"); 334 GLES20.glAttachShader(program, pixelShader); 335 checkGlError("glAttachShader"); 336 GLES20.glLinkProgram(program); 337 int[] linkStatus = new int[1]; 338 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 339 if (linkStatus[0] != GLES20.GL_TRUE) { 340 Log.e(TAG, "Could not link program: "); 341 Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 342 GLES20.glDeleteProgram(program); 343 program = 0; 344 } 345 } 346 return program; 347 } 348 createTexture2D()349 private int createTexture2D() { 350 // Texture object handle 351 int[] textureId = new int[1]; 352 353 InputStream in = null; 354 try { 355 in = mContext.getAssets().open(TEXTURE_FILE); 356 Bitmap bitmap = BitmapFactory.decodeStream(in); 357 358 // Generate a texture object 359 GLES20.glGenTextures(1, textureId, 0); 360 361 // Bind the texture object 362 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]); 363 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); 364 365 // Set the filtering mode 366 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 367 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 368 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 369 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 370 371 } catch (IOException e) { 372 throw new IllegalStateException("Couldn't load texture '" + TEXTURE_FILE 373 + "'", e); 374 } finally { 375 if (in != null) 376 try { 377 in.close(); 378 } catch (IOException e) { 379 } 380 } 381 382 return textureId[0]; 383 } 384 checkGlError(String op)385 private void checkGlError(String op) { 386 int error; 387 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 388 Log.e(TAG, op + ": glError " + error); 389 throw new IllegalStateException(op + ": glError " + error); 390 } 391 } 392 createVbo()393 private void createVbo() { 394 resetTimer(); 395 if (mParam.mUseVboForVertices) { 396 GLES20.glGenBuffers(mNumSpheres, mVboVertices, 0); 397 checkGlError("glGenBuffers Vertex"); 398 for (int i = 0; i < mNumSpheres; i++) { 399 GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboVertices[i]); 400 checkGlError("glBindBuffer Vertex"); 401 FloatBuffer vertices = mSpheres[i].getVertices(); 402 GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertices.limit() 403 * Sphere.FLOAT_SIZE, vertices, GLES20.GL_STATIC_DRAW); 404 checkGlError("glBufferData Vertex"); 405 } 406 } 407 if (mParam.mUseVboForIndices) { 408 GLES20.glGenBuffers(mNumIndices, mVboIndices, 0); 409 checkGlError("glGenBuffers Index"); 410 for (int i = 0; i < mNumSpheres; i++) { 411 int[] numIndices = mSpheres[i].getNumIndices(); 412 ShortBuffer[] indices = mSpheres[i].getIndices(); 413 int indexVboBase = i * mParam.mNumIndicesPerVertex; 414 for (int j = 0; j < numIndices.length; j++) { 415 GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 416 mVboIndices[indexVboBase + j]); 417 GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, 418 indices[j].limit() * Sphere.SHORT_SIZE, indices[j], 419 GLES20.GL_STATIC_DRAW); 420 checkGlError("glBufferData Index"); 421 422 } 423 } 424 } 425 GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 426 GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); 427 measureTime("VBO creation"); 428 } 429 resetTimer()430 private void resetTimer() { 431 mMeasurementStartTime = System.currentTimeMillis(); 432 } 433 measureTime(String description)434 private void measureTime(String description) { 435 long currentTime = System.currentTimeMillis(); 436 float timePassedInSecs = (float) (currentTime - mMeasurementStartTime) / 1000f; 437 Log.i(TAG, description + " time in secs: " + timePassedInSecs); 438 } 439 printGlInfos()440 private void printGlInfos() { 441 Log.i(TAG, "Vendor " + GLES20.glGetString(GLES20.GL_VENDOR)); 442 Log.i(TAG, "Version " + GLES20.glGetString(GLES20.GL_VERSION)); 443 Log.i(TAG, "Renderer " + mRendererName); 444 Log.i(TAG, "Extensions " + GLES20.glGetString(GLES20.GL_EXTENSIONS)); 445 } printParams()446 private void printParams() { 447 Log.i(TAG, "UseVboForVertex " + mParam.mUseVboForVertices); 448 Log.i(TAG, "UseVboForIndex " + mParam.mUseVboForIndices); 449 Log.i(TAG, "No Spheres " + mNumSpheres); 450 Log.i(TAG, "No indices buffer per vertex " + mParam.mNumIndicesPerVertex); 451 } 452 } 453