/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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.badlogic.gdx.backends.android; /******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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. ******************************************************************************/ import java.lang.reflect.Method; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView.EGLConfigChooser; import android.opengl.GLSurfaceView.Renderer; import android.service.dreams.DreamService; import android.util.DisplayMetrics; import android.view.Display; import android.view.View; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Graphics; import com.badlogic.gdx.LifecycleListener; import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceView20; import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceViewCupcake; import com.badlogic.gdx.backends.android.surfaceview.GdxEglConfigChooser; import com.badlogic.gdx.backends.android.surfaceview.ResolutionStrategy; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.GL11; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GLCommon; import com.badlogic.gdx.graphics.GLU; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.WindowedMean; import com.badlogic.gdx.utils.Array; /** * An implementation of {@link Graphics} for Android. * * @author mzechner */ public final class AndroidGraphicsDaydream implements Graphics, Renderer { final View view; int width; int height; AndroidDaydream app; GLCommon gl; GL10 gl10; GL11 gl11; GL20 gl20; GLU glu; EGLContext eglContext; String extensions; private long lastFrameTime = System.nanoTime(); private float deltaTime = 0; private long frameStart = System.nanoTime(); private int frames = 0; private int fps; private WindowedMean mean = new WindowedMean(5); volatile boolean created = false; volatile boolean running = false; volatile boolean pause = false; volatile boolean resume = false; volatile boolean destroy = false; private float ppiX = 0; private float ppiY = 0; private float ppcX = 0; private float ppcY = 0; private float density = 1; private final AndroidApplicationConfiguration config; private BufferFormat bufferFormat = new BufferFormat(5, 6, 5, 0, 16, 0, 0, false); private boolean isContinuous = true; public AndroidGraphicsDaydream(AndroidDaydream daydream, AndroidApplicationConfiguration config, ResolutionStrategy resolutionStrategy) { this.config = config; view = createGLSurfaceView(daydream, config.useGL20, resolutionStrategy); setPreserveContext(view); view.setFocusable(true); view.setFocusableInTouchMode(true); this.app = daydream; } private void setPreserveContext(View view) { int sdkVersion = Integer.parseInt(android.os.Build.VERSION.SDK); if (sdkVersion >= 11 && view instanceof GLSurfaceView20) { try { Method method = null; for (Method m : view.getClass().getMethods()) { if (m.getName().equals("setPreserveEGLContextOnPause")) { method = m; break; } } if (method != null) { method.invoke((GLSurfaceView20) view, true); } } catch (Exception e) { } } } private View createGLSurfaceView(DreamService dream, boolean useGL2, final ResolutionStrategy resolutionStrategy) { EGLConfigChooser configChooser = getEglConfigChooser(); if (useGL2 && checkGL20()) { GLSurfaceView20 view = new GLSurfaceView20(dream, resolutionStrategy); if (configChooser != null) view.setEGLConfigChooser(configChooser); else view.setEGLConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil); view.setRenderer(this); return view; } else { config.useGL20 = false; configChooser = getEglConfigChooser(); int sdkVersion = Integer.parseInt(android.os.Build.VERSION.SDK); if (sdkVersion >= 11) { GLSurfaceView view = new GLSurfaceView(dream) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ResolutionStrategy.MeasuredDimension measures = resolutionStrategy.calcMeasures( widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(measures.width, measures.height); } }; if (configChooser != null) view.setEGLConfigChooser(configChooser); else view.setEGLConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil); view.setRenderer(this); return view; } else { GLSurfaceViewCupcake view = new GLSurfaceViewCupcake(dream, resolutionStrategy); if (configChooser != null) view.setEGLConfigChooser(configChooser); else view.setEGLConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil); view.setRenderer(this); return view; } } } private EGLConfigChooser getEglConfigChooser() { return new GdxEglConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil, config.numSamples, config.useGL20); } private void updatePpi() { DisplayMetrics metrics = new DisplayMetrics(); app.getWindowManager().getDefaultDisplay().getMetrics(metrics); ppiX = metrics.xdpi; ppiY = metrics.ydpi; ppcX = metrics.xdpi / 2.54f; ppcY = metrics.ydpi / 2.54f; density = metrics.density; } private boolean checkGL20() { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int[2]; egl.eglInitialize(display, version); int EGL_OPENGL_ES2_BIT = 4; int[] configAttribs = { EGL10.EGL_RED_SIZE, 4, EGL10.EGL_GREEN_SIZE, 4, EGL10.EGL_BLUE_SIZE, 4, EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE }; EGLConfig[] configs = new EGLConfig[10]; int[] num_config = new int[1]; egl.eglChooseConfig(display, configAttribs, configs, 10, num_config); egl.eglTerminate(display); return num_config[0] > 0; } /** {@inheritDoc} */ @Override public GL10 getGL10() { return gl10; } /** {@inheritDoc} */ @Override public GL11 getGL11() { return gl11; } /** {@inheritDoc} */ @Override public GL20 getGL20() { return gl20; } /** {@inheritDoc} */ @Override public int getHeight() { return height; } /** {@inheritDoc} */ @Override public int getWidth() { return width; } /** {@inheritDoc} */ @Override public boolean isGL11Available() { return gl11 != null; } /** {@inheritDoc} */ @Override public boolean isGL20Available() { return gl20 != null; } private static boolean isPowerOfTwo(int value) { return ((value != 0) && (value & (value - 1)) == 0); } /** * This instantiates the GL10, GL11 and GL20 instances. Includes the check for certain devices that pretend to * support GL11 but fuck up vertex buffer objects. This includes the pixelflinger which segfaults when buffers are * deleted as well as the Motorola CLIQ and the Samsung Behold II. * * @param gl */ private void setupGL(javax.microedition.khronos.opengles.GL10 gl) { if (gl10 != null || gl20 != null) return; if (view instanceof GLSurfaceView20) { gl20 = new AndroidGL20(); this.gl = gl20; } else { gl10 = new AndroidGL10(gl); this.gl = gl10; if (gl instanceof javax.microedition.khronos.opengles.GL11) { String renderer = gl.glGetString(GL10.GL_RENDERER); if (renderer != null) { // silly GT-I7500 if (!renderer.toLowerCase().contains("pixelflinger") && !(android.os.Build.MODEL.equals("MB200") || android.os.Build.MODEL.equals("MB220") || android.os.Build.MODEL .contains("Behold"))) { gl11 = new AndroidGL11((javax.microedition.khronos.opengles.GL11) gl); gl10 = gl11; } } } } this.glu = new AndroidGLU(); Gdx.gl = this.gl; Gdx.gl10 = gl10; Gdx.gl11 = gl11; Gdx.gl20 = gl20; Gdx.glu = glu; Gdx.app.log("AndroidGraphics", "OGL renderer: " + gl.glGetString(GL10.GL_RENDERER)); Gdx.app.log("AndroidGraphics", "OGL vendor: " + gl.glGetString(GL10.GL_VENDOR)); Gdx.app.log("AndroidGraphics", "OGL version: " + gl.glGetString(GL10.GL_VERSION)); Gdx.app.log("AndroidGraphics", "OGL extensions: " + gl.glGetString(GL10.GL_EXTENSIONS)); } @Override public void onSurfaceChanged(javax.microedition.khronos.opengles.GL10 gl, int width, int height) { this.width = width; this.height = height; updatePpi(); gl.glViewport(0, 0, this.width, this.height); if (created == false) { app.listener.create(); created = true; synchronized (this) { running = true; } } app.listener.resize(width, height); } @Override public void onSurfaceCreated(javax.microedition.khronos.opengles.GL10 gl, EGLConfig config) { eglContext = ((EGL10) EGLContext.getEGL()).eglGetCurrentContext(); setupGL(gl); logConfig(config); updatePpi(); Mesh.invalidateAllMeshes(app); Texture.invalidateAllTextures(app); ShaderProgram.invalidateAllShaderPrograms(app); FrameBuffer.invalidateAllFrameBuffers(app); Gdx.app.log("AndroidGraphics", Mesh.getManagedStatus()); Gdx.app.log("AndroidGraphics", Texture.getManagedStatus()); Gdx.app.log("AndroidGraphics", ShaderProgram.getManagedStatus()); Gdx.app.log("AndroidGraphics", FrameBuffer.getManagedStatus()); Display display = app.getWindowManager().getDefaultDisplay(); this.width = display.getWidth(); this.height = display.getHeight(); mean = new WindowedMean(5); this.lastFrameTime = System.nanoTime(); gl.glViewport(0, 0, this.width, this.height); } private void logConfig(EGLConfig config) { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int r = getAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); int g = getAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); int b = getAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); int a = getAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); int d = getAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); int s = getAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); int samples = Math.max(getAttrib(egl, display, config, EGL10.EGL_SAMPLES, 0), getAttrib(egl, display, config, GdxEglConfigChooser.EGL_COVERAGE_SAMPLES_NV, 0)); boolean coverageSample = getAttrib(egl, display, config, GdxEglConfigChooser.EGL_COVERAGE_SAMPLES_NV, 0) != 0; Gdx.app.log("AndroidGraphics", "framebuffer: (" + r + ", " + g + ", " + b + ", " + a + ")"); Gdx.app.log("AndroidGraphics", "depthbuffer: (" + d + ")"); Gdx.app.log("AndroidGraphics", "stencilbuffer: (" + s + ")"); Gdx.app.log("AndroidGraphics", "samples: (" + samples + ")"); Gdx.app.log("AndroidGraphics", "coverage sampling: (" + coverageSample + ")"); bufferFormat = new BufferFormat(r, g, b, a, d, s, samples, coverageSample); } int[] value = new int[1]; private int getAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attrib, int defValue) { if (egl.eglGetConfigAttrib(display, config, attrib, value)) { return value[0]; } return defValue; } Object synch = new Object(); void resume() { synchronized (synch) { running = true; resume = true; } } void pause() { synchronized (synch) { if (!running) return; running = false; pause = true; while (pause) { try { synch.wait(); } catch (InterruptedException ignored) { Gdx.app.log("AndroidGraphics", "waiting for pause synchronization failed!"); } } } } void destroy() { synchronized (synch) { running = false; destroy = true; while (destroy) { try { synch.wait(); } catch (InterruptedException ex) { Gdx.app.log("AndroidGraphics", "waiting for destroy synchronization failed!"); } } } } @Override public void onDrawFrame(javax.microedition.khronos.opengles.GL10 gl) { long time = System.nanoTime(); deltaTime = (time - lastFrameTime) / 1000000000.0f; lastFrameTime = time; mean.addValue(deltaTime); boolean lrunning = false; boolean lpause = false; boolean ldestroy = false; boolean lresume = false; synchronized (synch) { lrunning = running; lpause = pause; ldestroy = destroy; lresume = resume; if (resume) { resume = false; } if (pause) { pause = false; synch.notifyAll(); } if (destroy) { destroy = false; synch.notifyAll(); } } if (lresume) { Array<LifecycleListener> listeners = app.lifecycleListeners; synchronized (listeners) { for (LifecycleListener listener : listeners) { listener.resume(); } } ((AndroidDaydream) app).audio.resume(); app.listener.resume(); Gdx.app.log("AndroidGraphics", "resumed"); } if (lrunning) { synchronized (app.runnables) { app.executedRunnables.clear(); app.executedRunnables.addAll(app.runnables); app.runnables.clear(); for (int i = 0; i < app.executedRunnables.size; i++) { try { app.executedRunnables.get(i).run(); } catch (Throwable t) { t.printStackTrace(); } } } app.input.processEvents(); app.listener.render(); } if (lpause) { Array<LifecycleListener> listeners = app.lifecycleListeners; synchronized (listeners) { for (LifecycleListener listener : listeners) { listener.pause(); } } app.listener.pause(); ((AndroidDaydream) app).audio.pause(); Gdx.app.log("AndroidGraphics", "paused"); } if (ldestroy) { Array<LifecycleListener> listeners = app.lifecycleListeners; synchronized (listeners) { for (LifecycleListener listener : listeners) { listener.dispose(); } } app.listener.dispose(); ((AndroidDaydream) app).audio.dispose(); ((AndroidDaydream) app).audio = null; Gdx.app.log("AndroidGraphics", "destroyed"); } if (time - frameStart > 1000000000) { fps = frames; frames = 0; frameStart = time; } frames++; } /** {@inheritDoc} */ @Override public float getDeltaTime() { return mean.getMean() == 0 ? deltaTime : mean.getMean(); } @Override public float getRawDeltaTime() { return deltaTime; } /** {@inheritDoc} */ @Override public GraphicsType getType() { return GraphicsType.AndroidGL; } /** {@inheritDoc} */ @Override public int getFramesPerSecond() { return fps; } public void clearManagedCaches() { Mesh.clearAllMeshes(app); Texture.clearAllTextures(app); ShaderProgram.clearAllShaderPrograms(app); FrameBuffer.clearAllFrameBuffers(app); Gdx.app.log("AndroidGraphics", Mesh.getManagedStatus()); Gdx.app.log("AndroidGraphics", Texture.getManagedStatus()); Gdx.app.log("AndroidGraphics", ShaderProgram.getManagedStatus()); Gdx.app.log("AndroidGraphics", FrameBuffer.getManagedStatus()); } public View getView() { return view; } /** {@inheritDoc} */ @Override public GLCommon getGLCommon() { return gl; } @Override public float getPpiX() { return ppiX; } @Override public float getPpiY() { return ppiY; } @Override public float getPpcX() { return ppcX; } @Override public float getPpcY() { return ppcY; } @Override public float getDensity() { return density; } @Override public GLU getGLU() { return glu; } @Override public boolean supportsDisplayModeChange() { return false; } @Override public boolean setDisplayMode(DisplayMode displayMode) { return false; } @Override public DisplayMode[] getDisplayModes() { return new DisplayMode[] { getDesktopDisplayMode() }; } @Override public boolean setDisplayMode(int width, int height, boolean fullscreen) { return false; } @Override public void setTitle(String title) { } private class AndroidDisplayMode extends DisplayMode { protected AndroidDisplayMode(int width, int height, int refreshRate, int bitsPerPixel) { super(width, height, refreshRate, bitsPerPixel); } } @Override public DisplayMode getDesktopDisplayMode() { DisplayMetrics metrics = new DisplayMetrics(); app.getWindowManager().getDefaultDisplay().getMetrics(metrics); return new AndroidDisplayMode(metrics.widthPixels, metrics.heightPixels, 0, 0); } @Override public BufferFormat getBufferFormat() { return bufferFormat; } @Override public void setVSync(boolean vsync) { } @Override public boolean supportsExtension(String extension) { if (extensions == null) extensions = Gdx.gl.glGetString(GL10.GL_EXTENSIONS); return extensions.contains(extension); } @Override public void setContinuousRendering(boolean isContinuous) { if (view != null) { this.isContinuous = isContinuous; int renderMode = isContinuous ? GLSurfaceView.RENDERMODE_CONTINUOUSLY : GLSurfaceView.RENDERMODE_WHEN_DIRTY; if (view instanceof GLSurfaceViewCupcake) ((GLSurfaceViewCupcake) view).setRenderMode(renderMode); if (view instanceof GLSurfaceView) ((GLSurfaceView) view).setRenderMode(renderMode); mean.clear(); } } public boolean isContinuousRendering() { return isContinuous; } @Override public void requestRendering() { if (view != null) { if (view instanceof GLSurfaceViewCupcake) ((GLSurfaceViewCupcake) view).requestRender(); if (view instanceof GLSurfaceView) ((GLSurfaceView) view).requestRender(); } } @Override public boolean isFullscreen() { return true; } }