/* * Copyright (C) 2010 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 com.replica.replicaisland; import android.content.Context; import android.os.Build; import android.os.SystemClock; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import com.replica.replicaisland.RenderSystem.RenderElement; /** * GameRenderer the top-level rendering interface for the game engine. It is called by * GLSurfaceView and is responsible for submitting commands to OpenGL. GameRenderer receives a * queue of renderable objects from the thread and uses that to draw the scene every frame. If * no queue is available then no drawing is performed. If the queue is not changed from frame to * frame, the same scene will be redrawn every frame. * The GameRenderer also invokes texture loads when it is activated. */ public class GameRenderer implements GLSurfaceView.Renderer { private static final int PROFILE_REPORT_DELAY = 3 * 1000; private int mWidth; private int mHeight; private int mHalfWidth; private int mHalfHeight; private float mScaleX; private float mScaleY; private Context mContext; private long mLastTime; private int mProfileFrames; private long mProfileWaitTime; private long mProfileFrameTime; private long mProfileSubmitTime; private int mProfileObjectCount; private ObjectManager mDrawQueue; private boolean mDrawQueueChanged; private Game mGame; private Object mDrawLock; float mCameraX; float mCameraY; boolean mCallbackRequested; public GameRenderer(Context context, Game game, int gameWidth, int gameHeight) { mContext = context; mGame = game; mWidth = gameWidth; mHeight = gameHeight; mHalfWidth = gameWidth / 2; mHalfHeight = gameHeight / 2; mScaleX = 1.0f; mScaleY = 1.0f; mDrawQueueChanged = false; mDrawLock = new Object(); mCameraX = 0.0f; mCameraY = 0.0f; mCallbackRequested = false; } public void onSurfaceCreated(GL10 gl, EGLConfig config) { /* * Some one-time OpenGL initialization can be made here probably based * on features of this particular context */ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glClearColor(0.0f, 0.0f, 0.0f, 1); gl.glShadeModel(GL10.GL_FLAT); gl.glDisable(GL10.GL_DEPTH_TEST); gl.glEnable(GL10.GL_TEXTURE_2D); /* * By default, OpenGL enables features that improve quality but reduce * performance. One might want to tweak that especially on software * renderer. */ gl.glDisable(GL10.GL_DITHER); gl.glDisable(GL10.GL_LIGHTING); gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); String extensions = gl.glGetString(GL10.GL_EXTENSIONS); String version = gl.glGetString(GL10.GL_VERSION); String renderer = gl.glGetString(GL10.GL_RENDERER); boolean isSoftwareRenderer = renderer.contains("PixelFlinger"); boolean isOpenGL10 = version.contains("1.0"); boolean supportsDrawTexture = extensions.contains("draw_texture"); // VBOs are standard in GLES1.1 // No use using VBOs when software renderering, esp. since older versions of the software renderer // had a crash bug related to freeing VBOs. boolean supportsVBOs = !isSoftwareRenderer && (!isOpenGL10 || extensions.contains("vertex_buffer_object")); ContextParameters params = BaseObject.sSystemRegistry.contextParameters; params.supportsDrawTexture = supportsDrawTexture; params.supportsVBOs = supportsVBOs; hackBrokenDevices(); DebugLog.i("Graphics Support", version + " (" + renderer + "): " +(supportsDrawTexture ? "draw texture," : "") + (supportsVBOs ? "vbos" : "")); mGame.onSurfaceCreated(); } private void hackBrokenDevices() { // Some devices are broken. Fix them here. This is pretty much the only // device-specific code in the whole project. Ugh. ContextParameters params = BaseObject.sSystemRegistry.contextParameters; if (Build.PRODUCT.contains("morrison")) { // This is the Motorola Cliq. This device LIES and says it supports // VBOs, which it actually does not (or, more likely, the extensions string // is correct and the GL JNI glue is broken). params.supportsVBOs = false; // TODO: if Motorola fixes this, I should switch to using the fingerprint // (blur/morrison/morrison/morrison:1.5/CUPCAKE/091007:user/ota-rel-keys,release-keys) // instead of the product name so that newer versions use VBOs. } } public void loadTextures(GL10 gl, TextureLibrary library) { if (gl != null) { library.loadAll(mContext, gl); DebugLog.d("AndouKun", "Textures Loaded."); } } public void flushTextures(GL10 gl, TextureLibrary library) { if (gl != null) { library.deleteAll(gl); DebugLog.d("AndouKun", "Textures Unloaded."); } } public void loadBuffers(GL10 gl, BufferLibrary library) { if (gl != null) { library.generateHardwareBuffers(gl); DebugLog.d("AndouKun", "Buffers Created."); } } public void flushBuffers(GL10 gl, BufferLibrary library) { if (gl != null) { library.releaseHardwareBuffers(gl); DebugLog.d("AndouKun", "Buffers Released."); } } public void onSurfaceLost() { mGame.onSurfaceLost(); } public void requestCallback() { mCallbackRequested = true; } /** Draws the scene. Note that the draw queue is locked for the duration of this function. */ public void onDrawFrame(GL10 gl) { long time = SystemClock.uptimeMillis(); long time_delta = (time - mLastTime); synchronized(mDrawLock) { if (!mDrawQueueChanged) { while (!mDrawQueueChanged) { try { mDrawLock.wait(); } catch (InterruptedException e) { // No big deal if this wait is interrupted. } } } mDrawQueueChanged = false; } final long wait = SystemClock.uptimeMillis(); if (mCallbackRequested) { mGame.onSurfaceReady(); mCallbackRequested = false; } DrawableBitmap.beginDrawing(gl, mWidth, mHeight); synchronized (this) { if (mDrawQueue != null && mDrawQueue.getObjects().getCount() > 0) { OpenGLSystem.setGL(gl); FixedSizeArray objects = mDrawQueue.getObjects(); Object[] objectArray = objects.getArray(); final int count = objects.getCount(); final float scaleX = mScaleX; final float scaleY = mScaleY; final float halfWidth = mHalfWidth; final float halfHeight = mHalfHeight; mProfileObjectCount += count; for (int i = 0; i < count; i++) { RenderElement element = (RenderElement)objectArray[i]; float x = element.x; float y = element.y; if (element.cameraRelative) { x = (x - mCameraX) + halfWidth; y = (y - mCameraY) + halfHeight; } element.mDrawable.draw(x, y, scaleX, scaleY); } OpenGLSystem.setGL(null); } else if (mDrawQueue == null) { // If we have no draw queue, clear the screen. If we have a draw queue that // is empty, we'll leave the frame buffer alone. gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); } } DrawableBitmap.endDrawing(gl); long time2 = SystemClock.uptimeMillis(); mLastTime = time2; mProfileFrameTime += time_delta; mProfileSubmitTime += time2 - time; mProfileWaitTime += wait - time; mProfileFrames++; if (mProfileFrameTime > PROFILE_REPORT_DELAY) { final int validFrames = mProfileFrames; final long averageFrameTime = mProfileFrameTime / validFrames; final long averageSubmitTime = mProfileSubmitTime / validFrames; final float averageObjectsPerFrame = (float)mProfileObjectCount / validFrames; final long averageWaitTime = mProfileWaitTime / validFrames; DebugLog.d("Render Profile", "Average Submit: " + averageSubmitTime + " Average Draw: " + averageFrameTime + " Objects/Frame: " + averageObjectsPerFrame + " Wait Time: " + averageWaitTime); mProfileFrameTime = 0; mProfileSubmitTime = 0; mProfileFrames = 0; mProfileObjectCount = 0; } } public void onSurfaceChanged(GL10 gl, int w, int h) { DebugLog.d("AndouKun", "Surface Size Change: " + w + ", " + h); //mWidth = w;0 //mHeight = h; // ensure the same aspect ratio as the game float scaleX = (float)w / mWidth; float scaleY = (float)h / mHeight; final int viewportWidth = (int)(mWidth * scaleX); final int viewportHeight = (int)(mHeight * scaleY); gl.glViewport(0, 0, viewportWidth, viewportHeight); mScaleX = scaleX; mScaleY = scaleY; /* * Set our projection matrix. This doesn't have to be done each time we * draw, but usually a new projection needs to be set when the viewport * is resized. */ float ratio = (float) mWidth / mHeight; gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); mGame.onSurfaceReady(); } public synchronized void setDrawQueue(ObjectManager queue, float cameraX, float cameraY) { mDrawQueue = queue; mCameraX = cameraX; mCameraY = cameraY; synchronized(mDrawLock) { mDrawQueueChanged = true; mDrawLock.notify(); } } public synchronized void onPause() { // Stop waiting to avoid deadlock. // TODO: this is a hack. Probably this renderer // should just use GLSurfaceView's non-continuious render // mode. synchronized(mDrawLock) { mDrawQueueChanged = true; mDrawLock.notify(); } } /** * This function blocks while drawFrame() is in progress, and may be used by other threads to * determine when drawing is occurring. */ public synchronized void waitDrawingComplete() { } public void setContext(Context newContext) { mContext = newContext; } }