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