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
17package com.replica.replicaisland;
18
19import android.content.Context;
20import android.os.Build;
21import android.os.SystemClock;
22
23import javax.microedition.khronos.egl.EGLConfig;
24import javax.microedition.khronos.opengles.GL10;
25
26import 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 */
36public 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