1 /* 2 * Copyright (C) 2010 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 com.replica.replicaisland; 18 19 import android.content.Context; 20 import android.os.Build; 21 import android.os.SystemClock; 22 23 import javax.microedition.khronos.egl.EGLConfig; 24 import javax.microedition.khronos.opengles.GL10; 25 26 import com.replica.replicaisland.RenderSystem.RenderElement; 27 28 /** 29 * GameRenderer the top-level rendering interface for the game engine. It is called by 30 * GLSurfaceView and is responsible for submitting commands to OpenGL. GameRenderer receives a 31 * queue of renderable objects from the thread and uses that to draw the scene every frame. If 32 * no queue is available then no drawing is performed. If the queue is not changed from frame to 33 * frame, the same scene will be redrawn every frame. 34 * The GameRenderer also invokes texture loads when it is activated. 35 */ 36 public class GameRenderer implements GLSurfaceView.Renderer { 37 private static final int PROFILE_REPORT_DELAY = 3 * 1000; 38 39 private int mWidth; 40 private int mHeight; 41 private int mHalfWidth; 42 private int mHalfHeight; 43 44 private float mScaleX; 45 private float mScaleY; 46 private Context mContext; 47 private long mLastTime; 48 private int mProfileFrames; 49 private long mProfileWaitTime; 50 private long mProfileFrameTime; 51 private long mProfileSubmitTime; 52 private int mProfileObjectCount; 53 54 private ObjectManager mDrawQueue; 55 private boolean mDrawQueueChanged; 56 private Game mGame; 57 private Object mDrawLock; 58 59 float mCameraX; 60 float mCameraY; 61 62 boolean mCallbackRequested; 63 64 public GameRenderer(Context context, Game game, int gameWidth, int gameHeight) { 65 mContext = context; 66 mGame = game; 67 mWidth = gameWidth; 68 mHeight = gameHeight; 69 mHalfWidth = gameWidth / 2; 70 mHalfHeight = gameHeight / 2; 71 mScaleX = 1.0f; 72 mScaleY = 1.0f; 73 mDrawQueueChanged = false; 74 mDrawLock = new Object(); 75 mCameraX = 0.0f; 76 mCameraY = 0.0f; 77 mCallbackRequested = false; 78 } 79 80 public void onSurfaceCreated(GL10 gl, EGLConfig config) { 81 /* 82 * Some one-time OpenGL initialization can be made here probably based 83 * on features of this particular context 84 */ 85 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); 86 87 gl.glClearColor(0.0f, 0.0f, 0.0f, 1); 88 gl.glShadeModel(GL10.GL_FLAT); 89 gl.glDisable(GL10.GL_DEPTH_TEST); 90 gl.glEnable(GL10.GL_TEXTURE_2D); 91 /* 92 * By default, OpenGL enables features that improve quality but reduce 93 * performance. One might want to tweak that especially on software 94 * renderer. 95 */ 96 gl.glDisable(GL10.GL_DITHER); 97 gl.glDisable(GL10.GL_LIGHTING); 98 99 gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); 100 101 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); 102 103 String extensions = gl.glGetString(GL10.GL_EXTENSIONS); 104 String version = gl.glGetString(GL10.GL_VERSION); 105 String renderer = gl.glGetString(GL10.GL_RENDERER); 106 boolean isSoftwareRenderer = renderer.contains("PixelFlinger"); 107 boolean isOpenGL10 = version.contains("1.0"); 108 boolean supportsDrawTexture = extensions.contains("draw_texture"); 109 // VBOs are standard in GLES1.1 110 // No use using VBOs when software renderering, esp. since older versions of the software renderer 111 // had a crash bug related to freeing VBOs. 112 boolean supportsVBOs = !isSoftwareRenderer && (!isOpenGL10 || extensions.contains("vertex_buffer_object")); 113 ContextParameters params = BaseObject.sSystemRegistry.contextParameters; 114 params.supportsDrawTexture = supportsDrawTexture; 115 params.supportsVBOs = supportsVBOs; 116 117 hackBrokenDevices(); 118 119 DebugLog.i("Graphics Support", version + " (" + renderer + "): " +(supportsDrawTexture ? "draw texture," : "") + (supportsVBOs ? "vbos" : "")); 120 121 mGame.onSurfaceCreated(); 122 123 } 124 125 private void hackBrokenDevices() { 126 // Some devices are broken. Fix them here. This is pretty much the only 127 // device-specific code in the whole project. Ugh. 128 ContextParameters params = BaseObject.sSystemRegistry.contextParameters; 129 130 131 if (Build.PRODUCT.contains("morrison")) { 132 // This is the Motorola Cliq. This device LIES and says it supports 133 // VBOs, which it actually does not (or, more likely, the extensions string 134 // is correct and the GL JNI glue is broken). 135 params.supportsVBOs = false; 136 // TODO: if Motorola fixes this, I should switch to using the fingerprint 137 // (blur/morrison/morrison/morrison:1.5/CUPCAKE/091007:user/ota-rel-keys,release-keys) 138 // instead of the product name so that newer versions use VBOs. 139 } 140 } 141 142 public void loadTextures(GL10 gl, TextureLibrary library) { 143 if (gl != null) { 144 library.loadAll(mContext, gl); 145 DebugLog.d("AndouKun", "Textures Loaded."); 146 } 147 } 148 149 public void flushTextures(GL10 gl, TextureLibrary library) { 150 if (gl != null) { 151 library.deleteAll(gl); 152 DebugLog.d("AndouKun", "Textures Unloaded."); 153 } 154 } 155 156 public void loadBuffers(GL10 gl, BufferLibrary library) { 157 if (gl != null) { 158 library.generateHardwareBuffers(gl); 159 DebugLog.d("AndouKun", "Buffers Created."); 160 } 161 } 162 163 public void flushBuffers(GL10 gl, BufferLibrary library) { 164 if (gl != null) { 165 library.releaseHardwareBuffers(gl); 166 DebugLog.d("AndouKun", "Buffers Released."); 167 } 168 } 169 170 public void onSurfaceLost() { 171 mGame.onSurfaceLost(); 172 } 173 174 public void requestCallback() { 175 mCallbackRequested = true; 176 } 177 178 /** Draws the scene. Note that the draw queue is locked for the duration of this function. */ 179 public void onDrawFrame(GL10 gl) { 180 181 long time = SystemClock.uptimeMillis(); 182 long time_delta = (time - mLastTime); 183 184 synchronized(mDrawLock) { 185 if (!mDrawQueueChanged) { 186 while (!mDrawQueueChanged) { 187 try { 188 mDrawLock.wait(); 189 } catch (InterruptedException e) { 190 // No big deal if this wait is interrupted. 191 } 192 } 193 } 194 mDrawQueueChanged = false; 195 } 196 197 final long wait = SystemClock.uptimeMillis(); 198 199 if (mCallbackRequested) { 200 mGame.onSurfaceReady(); 201 mCallbackRequested = false; 202 } 203 204 DrawableBitmap.beginDrawing(gl, mWidth, mHeight); 205 206 synchronized (this) { 207 if (mDrawQueue != null && mDrawQueue.getObjects().getCount() > 0) { 208 OpenGLSystem.setGL(gl); 209 FixedSizeArray<BaseObject> objects = mDrawQueue.getObjects(); 210 Object[] objectArray = objects.getArray(); 211 final int count = objects.getCount(); 212 final float scaleX = mScaleX; 213 final float scaleY = mScaleY; 214 final float halfWidth = mHalfWidth; 215 final float halfHeight = mHalfHeight; 216 mProfileObjectCount += count; 217 for (int i = 0; i < count; i++) { 218 RenderElement element = (RenderElement)objectArray[i]; 219 float x = element.x; 220 float y = element.y; 221 if (element.cameraRelative) { 222 x = (x - mCameraX) + halfWidth; 223 y = (y - mCameraY) + halfHeight; 224 } 225 element.mDrawable.draw(x, y, scaleX, scaleY); 226 } 227 OpenGLSystem.setGL(null); 228 } else if (mDrawQueue == null) { 229 // If we have no draw queue, clear the screen. If we have a draw queue that 230 // is empty, we'll leave the frame buffer alone. 231 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); 232 } 233 } 234 235 DrawableBitmap.endDrawing(gl); 236 237 long time2 = SystemClock.uptimeMillis(); 238 mLastTime = time2; 239 240 mProfileFrameTime += time_delta; 241 mProfileSubmitTime += time2 - time; 242 mProfileWaitTime += wait - time; 243 244 mProfileFrames++; 245 if (mProfileFrameTime > PROFILE_REPORT_DELAY) { 246 final int validFrames = mProfileFrames; 247 final long averageFrameTime = mProfileFrameTime / validFrames; 248 final long averageSubmitTime = mProfileSubmitTime / validFrames; 249 final float averageObjectsPerFrame = (float)mProfileObjectCount / validFrames; 250 final long averageWaitTime = mProfileWaitTime / validFrames; 251 252 DebugLog.d("Render Profile", 253 "Average Submit: " + averageSubmitTime 254 + " Average Draw: " + averageFrameTime 255 + " Objects/Frame: " + averageObjectsPerFrame 256 + " Wait Time: " + averageWaitTime); 257 258 mProfileFrameTime = 0; 259 mProfileSubmitTime = 0; 260 mProfileFrames = 0; 261 mProfileObjectCount = 0; 262 } 263 264 } 265 266 public void onSurfaceChanged(GL10 gl, int w, int h) { 267 DebugLog.d("AndouKun", "Surface Size Change: " + w + ", " + h); 268 269 //mWidth = w;0 270 //mHeight = h; 271 // ensure the same aspect ratio as the game 272 float scaleX = (float)w / mWidth; 273 float scaleY = (float)h / mHeight; 274 final int viewportWidth = (int)(mWidth * scaleX); 275 final int viewportHeight = (int)(mHeight * scaleY); 276 gl.glViewport(0, 0, viewportWidth, viewportHeight); 277 mScaleX = scaleX; 278 mScaleY = scaleY; 279 280 281 /* 282 * Set our projection matrix. This doesn't have to be done each time we 283 * draw, but usually a new projection needs to be set when the viewport 284 * is resized. 285 */ 286 float ratio = (float) mWidth / mHeight; 287 gl.glMatrixMode(GL10.GL_PROJECTION); 288 gl.glLoadIdentity(); 289 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); 290 291 292 mGame.onSurfaceReady(); 293 } 294 295 public synchronized void setDrawQueue(ObjectManager queue, float cameraX, float cameraY) { 296 mDrawQueue = queue; 297 mCameraX = cameraX; 298 mCameraY = cameraY; 299 synchronized(mDrawLock) { 300 mDrawQueueChanged = true; 301 mDrawLock.notify(); 302 } 303 } 304 305 public synchronized void onPause() { 306 // Stop waiting to avoid deadlock. 307 // TODO: this is a hack. Probably this renderer 308 // should just use GLSurfaceView's non-continuious render 309 // mode. 310 synchronized(mDrawLock) { 311 mDrawQueueChanged = true; 312 mDrawLock.notify(); 313 } 314 } 315 316 /** 317 * This function blocks while drawFrame() is in progress, and may be used by other threads to 318 * determine when drawing is occurring. 319 */ 320 321 public synchronized void waitDrawingComplete() { 322 } 323 324 public void setContext(Context newContext) { 325 mContext = newContext; 326 } 327 328 329 } 330