Skip to content

Instantly share code, notes, and snippets.

@cypherdare
Last active June 6, 2020 22:40
Show Gist options
  • Save cypherdare/71bd7b0f98619fec61ffcfdd3ecf31af to your computer and use it in GitHub Desktop.
Save cypherdare/71bd7b0f98619fec61ffcfdd3ecf31af to your computer and use it in GitHub Desktop.
Attempt at fixing deadlocks by using androidx lifecycle state to abstract synchronization
/*******************************************************************************
* 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;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.ApplicationLogger;
import com.badlogic.gdx.Audio;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.LifecycleListener;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.backends.android.surfaceview.FillResolutionStrategy;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Clipboard;
import com.badlogic.gdx.utils.GdxNativesLoader;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.SnapshotArray;
import java.lang.reflect.Method;
public abstract class LifecycleStateAndroidApplication extends AppCompatActivity implements AndroidApplicationBase {
static {
GdxNativesLoader.load();
}
protected LifecycleStateAndroidGraphics graphics;
protected AndroidInput input;
protected AndroidAudio audio;
protected AndroidFiles files;
protected AndroidNet net;
protected AndroidClipboard clipboard;
protected ApplicationListener listener;
public Handler handler;
protected final Array<Runnable> runnables = new Array<Runnable>();
protected final Array<Runnable> executedRunnables = new Array<Runnable>();
protected final SnapshotArray<LifecycleListener> lifecycleListeners = new SnapshotArray<LifecycleListener>(LifecycleListener.class);
private final Array<AndroidEventListener> androidEventListeners = new Array<AndroidEventListener>();
protected int logLevel = LOG_INFO;
protected ApplicationLogger applicationLogger;
protected boolean useImmersiveMode = false;
protected boolean hideStatusBar = false;
private int wasFocusChanged = -1;
private boolean isWaitingForAudio = false;
/** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get
* input, render via OpenGL and so on. Uses a default {@link AndroidApplicationConfiguration}.
*
* @param listener the {@link ApplicationListener} implementing the program logic **/
public void initialize (ApplicationListener listener) {
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(listener, config);
}
/** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get
* input, render via OpenGL and so on. You can configure other aspects of the application with the rest of the fields in the
* {@link AndroidApplicationConfiguration} instance.
*
* @param listener the {@link ApplicationListener} implementing the program logic
* @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer,
* etc.). */
public void initialize (ApplicationListener listener, AndroidApplicationConfiguration config) {
init(listener, config, false);
}
/** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get
* input, render via OpenGL and so on. Uses a default {@link AndroidApplicationConfiguration}.
* <p>
* Note: you have to add the returned view to your layout!
*
* @param listener the {@link ApplicationListener} implementing the program logic
* @return the GLSurfaceView of the application */
public View initializeForView (ApplicationListener listener) {
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
return initializeForView(listener, config);
}
/** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get
* input, render via OpenGL and so on. You can configure other aspects of the application with the rest of the fields in the
* {@link AndroidApplicationConfiguration} instance.
* <p>
* Note: you have to add the returned view to your layout!
*
* @param listener the {@link ApplicationListener} implementing the program logic
* @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer,
* etc.).
* @return the GLSurfaceView of the application */
public View initializeForView (ApplicationListener listener, AndroidApplicationConfiguration config) {
init(listener, config, true);
return graphics.getView();
}
private void init (ApplicationListener listener, AndroidApplicationConfiguration config, boolean isForView) {
if (this.getVersion() < MINIMUM_SDK) {
throw new GdxRuntimeException("LibGDX requires Android API Level " + MINIMUM_SDK + " or later.");
}
setApplicationLogger(new AndroidApplicationLogger());
graphics = new LifecycleStateAndroidGraphics(this, this.getLifecycle(), config, config.resolutionStrategy == null ? new FillResolutionStrategy()
: config.resolutionStrategy);
input = AndroidInputFactory.newAndroidInput(this, this, graphics.view, config);
audio = new AndroidAudio(this, config);
this.getFilesDir(); // workaround for Android bug #10515463
files = new AndroidFiles(this.getAssets(), this.getFilesDir().getAbsolutePath());
net = new AndroidNet(this, config);
this.listener = listener;
this.handler = new Handler();
this.useImmersiveMode = config.useImmersiveMode;
this.hideStatusBar = config.hideStatusBar;
this.clipboard = new AndroidClipboard(this);
// Add a specialized audio lifecycle listener
addLifecycleListener(new LifecycleListener() {
@Override
public void resume () {
// No need to resume audio here
}
@Override
public void pause () {
audio.pause();
}
@Override
public void dispose () {
audio.dispose();
}
});
Gdx.app = this;
Gdx.input = this.getInput();
Gdx.audio = this.getAudio();
Gdx.files = this.getFiles();
Gdx.graphics = this.getGraphics();
Gdx.net = this.getNet();
if (!isForView) {
try {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} catch (Exception ex) {
log("AndroidApplication", "Content already displayed, cannot request FEATURE_NO_TITLE", ex);
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
setContentView(graphics.getView(), createLayoutParams());
}
createWakeLock(config.useWakelock);
hideStatusBar(this.hideStatusBar);
useImmersiveMode(this.useImmersiveMode);
if (this.useImmersiveMode && getVersion() >= Build.VERSION_CODES.KITKAT) {
try {
Class<?> vlistener = Class.forName("com.badlogic.gdx.backends.android.AndroidVisibilityListener");
Object o = vlistener.newInstance();
Method method = vlistener.getDeclaredMethod("createListener", AndroidApplicationBase.class);
method.invoke(o, this);
} catch (Exception e) {
log("AndroidApplication", "Failed to create AndroidVisibilityListener", e);
}
}
// detect an already connected bluetooth keyboardAvailable
if (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS)
this.getInput().keyboardAvailable = true;
}
protected FrameLayout.LayoutParams createLayoutParams () {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.gravity = Gravity.CENTER;
return layoutParams;
}
protected void createWakeLock (boolean use) {
if (use) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
protected void hideStatusBar (boolean hide) {
if (!hide || getVersion() < 11) return;
View rootView = getWindow().getDecorView();
try {
Method m = View.class.getMethod("setSystemUiVisibility", int.class);
if (getVersion() <= 13) m.invoke(rootView, 0x0);
m.invoke(rootView, 0x1);
} catch (Exception e) {
log("AndroidApplication", "Can't hide status bar", e);
}
}
@Override
public void onWindowFocusChanged (boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
useImmersiveMode(this.useImmersiveMode);
hideStatusBar(this.hideStatusBar);
if (hasFocus) {
this.wasFocusChanged = 1;
if (this.isWaitingForAudio) {
this.audio.resume();
this.isWaitingForAudio = false;
}
} else {
this.wasFocusChanged = 0;
}
}
@TargetApi(19)
@Override
public void useImmersiveMode (boolean use) {
if (!use || getVersion() < Build.VERSION_CODES.KITKAT) return;
View view = getWindow().getDecorView();
try {
Method m = View.class.getMethod("setSystemUiVisibility", int.class);
int code = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
m.invoke(view, code);
} catch (Exception e) {
log("AndroidApplication", "Can't set immersive mode", e);
}
}
@Override
protected void onPause () {
boolean isContinuous = graphics.isContinuousRendering();
boolean isContinuousEnforced = AndroidGraphics.enforceContinuousRendering;
// from here we don't want non continuous rendering
AndroidGraphics.enforceContinuousRendering = true;
graphics.setContinuousRendering(true);
// calls to setContinuousRendering(false) from other thread (ex: GLThread)
// will be ignored at this point...
// graphics.pause();
input.onPause();
if (isFinishing()) {
graphics.clearManagedCaches();
// graphics.destroy();
}
AndroidGraphics.enforceContinuousRendering = isContinuousEnforced;
graphics.setContinuousRendering(isContinuous);
graphics.onPauseGLSurfaceView();
super.onPause();
}
@Override
protected void onResume () {
Gdx.app = this;
Gdx.input = this.getInput();
Gdx.audio = this.getAudio();
Gdx.files = this.getFiles();
Gdx.graphics = this.getGraphics();
Gdx.net = this.getNet();
input.onResume();
if (graphics != null) {
graphics.onResumeGLSurfaceView();
}
this.isWaitingForAudio = true;
if (this.wasFocusChanged == 1 || this.wasFocusChanged == -1) {
this.audio.resume();
this.isWaitingForAudio = false;
}
super.onResume();
}
@Override
protected void onDestroy () {
super.onDestroy();
}
@Override
public ApplicationListener getApplicationListener () {
return listener;
}
@Override
public Audio getAudio () {
return audio;
}
@Override
public Files getFiles () {
return files;
}
@Override
public Graphics getGraphics () {
return graphics;
}
@Override
public AndroidInput getInput () {
return input;
}
@Override
public Net getNet () {
return net;
}
@Override
public ApplicationType getType () {
return ApplicationType.Android;
}
@Override
public int getVersion () {
return Build.VERSION.SDK_INT;
}
@Override
public long getJavaHeap () {
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
@Override
public long getNativeHeap () {
return Debug.getNativeHeapAllocatedSize();
}
@Override
public Preferences getPreferences (String name) {
return new AndroidPreferences(getSharedPreferences(name, Context.MODE_PRIVATE));
}
@Override
public Clipboard getClipboard () {
return clipboard;
}
@Override
public void postRunnable (Runnable runnable) {
synchronized (runnables) {
runnables.add(runnable);
Gdx.graphics.requestRendering();
}
}
@Override
public void onConfigurationChanged (Configuration config) {
super.onConfigurationChanged(config);
boolean keyboardAvailable = false;
if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) keyboardAvailable = true;
input.keyboardAvailable = keyboardAvailable;
}
@Override
public void exit () {
handler.post(new Runnable() {
@Override
public void run () {
LifecycleStateAndroidApplication.this.finish();
}
});
}
@Override
public void debug (String tag, String message) {
if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message);
}
@Override
public void debug (String tag, String message, Throwable exception) {
if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message, exception);
}
@Override
public void log (String tag, String message) {
if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message);
}
@Override
public void log (String tag, String message, Throwable exception) {
if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message, exception);
}
@Override
public void error (String tag, String message) {
if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message);
}
@Override
public void error (String tag, String message, Throwable exception) {
if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message, exception);
}
@Override
public void setLogLevel (int logLevel) {
this.logLevel = logLevel;
}
@Override
public int getLogLevel () {
return logLevel;
}
@Override
public void setApplicationLogger (ApplicationLogger applicationLogger) {
this.applicationLogger = applicationLogger;
}
@Override
public ApplicationLogger getApplicationLogger () {
return applicationLogger;
}
@Override
public void addLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.add(listener);
}
}
@Override
public void removeLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.removeValue(listener, true);
}
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// forward events to our listeners if there are any installed
synchronized (androidEventListeners) {
for (int i = 0; i < androidEventListeners.size; i++) {
androidEventListeners.get(i).onActivityResult(requestCode, resultCode, data);
}
}
}
/** Adds an event listener for Android specific event such as onActivityResult(...). */
public void addAndroidEventListener (AndroidEventListener listener) {
synchronized (androidEventListeners) {
androidEventListeners.add(listener);
}
}
/** Removes an event listener for Android specific event such as onActivityResult(...). */
public void removeAndroidEventListener (AndroidEventListener listener) {
synchronized (androidEventListeners) {
androidEventListeners.removeValue(listener, true);
}
}
@Override
public Context getContext () {
return this;
}
@Override
public Array<Runnable> getRunnables () {
return runnables;
}
@Override
public Array<Runnable> getExecutedRunnables () {
return executedRunnables;
}
@Override
public SnapshotArray<LifecycleListener> getLifecycleListeners () {
return lifecycleListeners;
}
@Override
public Window getApplicationWindow () {
return this.getWindow();
}
@Override
public Handler getHandler () {
return this.handler;
}
}
/*******************************************************************************
* 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;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.EGLConfigChooser;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.badlogic.gdx.Application;
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.GLSurfaceView20API18;
import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceViewAPI18;
import com.badlogic.gdx.backends.android.surfaceview.GdxEglConfigChooser;
import com.badlogic.gdx.backends.android.surfaceview.ResolutionStrategy;
import com.badlogic.gdx.graphics.Cubemap;
import com.badlogic.gdx.graphics.Cursor;
import com.badlogic.gdx.graphics.Cursor.SystemCursor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.TextureArray;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.GLVersion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.WindowedMean;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.SnapshotArray;
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 javax.microedition.khronos.opengles.GL10;
/** An implementation of {@link Graphics} for Android.
*
* @author mzechner */
public class LifecycleStateAndroidGraphics implements Graphics, Renderer, LifecycleObserver {
private static final String LOG_TAG = "AndroidGraphics";
final View view;
int width;
int height;
final AndroidApplicationBase app;
final Lifecycle lifecycle;
private volatile Lifecycle.State currentState;
private Lifecycle.State lastState = Lifecycle.State.INITIALIZED; // GL thread only
GL20 gl20;
GL30 gl30;
EGLContext eglContext;
GLVersion glVersion;
String extensions;
protected long lastFrameTime = System.nanoTime();
protected float deltaTime = 0;
protected long frameStart = System.nanoTime();
protected long frameId = -1;
protected int frames = 0;
protected int fps;
protected WindowedMean mean = new WindowedMean(5);
volatile boolean created = false;
// TODO volatile boolean running = false;
protected boolean firstResume = true; // gl thread only
private float ppiX = 0;
private float ppiY = 0;
private float ppcX = 0;
private float ppcY = 0;
private float density = 1;
protected final AndroidApplicationConfiguration config;
private BufferFormat bufferFormat = new BufferFormat(5, 6, 5, 0, 16, 0, 0, false);
private boolean isContinuous = true;
public LifecycleStateAndroidGraphics (AndroidApplicationBase application, Lifecycle lifecycle, AndroidApplicationConfiguration config,
ResolutionStrategy resolutionStrategy) {
this(application, lifecycle, config, resolutionStrategy, true);
}
public LifecycleStateAndroidGraphics (AndroidApplicationBase application, Lifecycle lifecycle, AndroidApplicationConfiguration config,
ResolutionStrategy resolutionStrategy, boolean focusableView) {
this.config = config;
this.app = application;
this.lifecycle = lifecycle;
lifecycle.addObserver(this);
view = createGLSurfaceView(application, resolutionStrategy);
preserveEGLContextOnPause();
if (focusableView) {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
void onLifecycleEvent() {
currentState = lifecycle.getCurrentState();
if (currentState == Lifecycle.State.DESTROYED)
lifecycle.removeObserver(LifecycleStateAndroidGraphics.this);
}
protected void preserveEGLContextOnPause () {
int sdkVersion = android.os.Build.VERSION.SDK_INT;
if ((sdkVersion >= 11 && view instanceof GLSurfaceView20) || view instanceof GLSurfaceView20API18) {
try {
view.getClass().getMethod("setPreserveEGLContextOnPause", boolean.class).invoke(view, true);
} catch (Exception e) {
Gdx.app.log(LOG_TAG, "Method GLSurfaceView.setPreserveEGLContextOnPause not found");
}
}
}
protected View createGLSurfaceView (AndroidApplicationBase application, final ResolutionStrategy resolutionStrategy) {
if (!checkGL20()) throw new GdxRuntimeException("Libgdx requires OpenGL ES 2.0");
EGLConfigChooser configChooser = getEglConfigChooser();
int sdkVersion = android.os.Build.VERSION.SDK_INT;
if (sdkVersion <= 10 && config.useGLSurfaceView20API18) {
GLSurfaceView20API18 view = new GLSurfaceView20API18(application.getContext(), 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 {
GLSurfaceView20 view = new GLSurfaceView20(application.getContext(), resolutionStrategy, config.useGL30 ? 3 : 2);
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;
}
}
public void onPauseGLSurfaceView () {
Gdx.app.log(LOG_TAG, "onPauseGLSurfaceView");
if (view != null) {
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).onPause();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).onPause();
}
}
public void onResumeGLSurfaceView () {
Gdx.app.log(LOG_TAG, "onResumeGLSurfaceView");
if (view != null) {
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).onResume();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).onResume();
}
}
protected EGLConfigChooser getEglConfigChooser () {
return new GdxEglConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil, config.numSamples);
}
protected 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;
}
protected 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 GL20 getGL20 () {
return gl20;
}
/** {@inheritDoc} */
@Override
public void setGL20 (GL20 gl20) {
this.gl20 = gl20;
if (gl30 == null) {
Gdx.gl = gl20;
Gdx.gl20 = gl20;
}
}
/** {@inheritDoc} */
@Override
public boolean isGL30Available () {
return gl30 != null;
}
/** {@inheritDoc} */
@Override
public GL30 getGL30 () {
return gl30;
}
/** {@inheritDoc} */
@Override
public void setGL30 (GL30 gl30) {
this.gl30 = gl30;
if (gl30 != null) {
this.gl20 = gl30;
Gdx.gl = gl20;
Gdx.gl20 = gl20;
Gdx.gl30 = gl30;
}
}
/** {@inheritDoc} */
@Override
public int getHeight () {
return height;
}
/** {@inheritDoc} */
@Override
public int getWidth () {
return width;
}
@Override
public int getBackBufferWidth () {
return width;
}
@Override
public int getBackBufferHeight () {
return height;
}
/** 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 */
protected void setupGL (GL10 gl) {
String versionString = gl.glGetString(GL10.GL_VERSION);
String vendorString = gl.glGetString(GL10.GL_VENDOR);
String rendererString = gl.glGetString(GL10.GL_RENDERER);
glVersion = new GLVersion(Application.ApplicationType.Android, versionString, vendorString, rendererString);
if (config.useGL30 && glVersion.getMajorVersion() > 2) {
if (gl30 != null) return;
gl20 = gl30 = new AndroidGL30();
Gdx.gl = gl30;
Gdx.gl20 = gl30;
Gdx.gl30 = gl30;
} else {
if (gl20 != null) return;
gl20 = new AndroidGL20();
Gdx.gl = gl20;
Gdx.gl20 = gl20;
}
Gdx.app.log(LOG_TAG, "OGL renderer: " + gl.glGetString(GL10.GL_RENDERER));
Gdx.app.log(LOG_TAG, "OGL vendor: " + gl.glGetString(GL10.GL_VENDOR));
Gdx.app.log(LOG_TAG, "OGL version: " + gl.glGetString(GL10.GL_VERSION));
Gdx.app.log(LOG_TAG, "OGL extensions: " + gl.glGetString(GL10.GL_EXTENSIONS));
}
@Override
public void onSurfaceChanged (GL10 gl, int width, int height) {
this.width = width;
this.height = height;
updatePpi();
gl.glViewport(0, 0, this.width, this.height);
if (!created) {
app.getApplicationListener().create();
created = true;
// TODO: maybe keep this and check both the local running and this to use for running state
// synchronized (this) {
// running = true;
// }
}
app.getApplicationListener().resize(width, height);
}
@Override
public void onSurfaceCreated (GL10 gl, EGLConfig config) {
eglContext = ((EGL10)EGLContext.getEGL()).eglGetCurrentContext();
setupGL(gl);
logConfig(config);
updatePpi();
Mesh.invalidateAllMeshes(app);
Texture.invalidateAllTextures(app);
Cubemap.invalidateAllCubemaps(app);
TextureArray.invalidateAllTextureArrays(app);
ShaderProgram.invalidateAllShaderPrograms(app);
FrameBuffer.invalidateAllFrameBuffers(app);
logManagedCachesStatus();
Display display = app.getWindowManager().getDefaultDisplay();
this.width = display.getWidth();
this.height = display.getHeight();
this.mean = new WindowedMean(5);
this.lastFrameTime = System.nanoTime();
gl.glViewport(0, 0, this.width, this.height);
}
protected 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(LOG_TAG, "framebuffer: (" + r + ", " + g + ", " + b + ", " + a + ")");
Gdx.app.log(LOG_TAG, "depthbuffer: (" + d + ")");
Gdx.app.log(LOG_TAG, "stencilbuffer: (" + s + ")");
Gdx.app.log(LOG_TAG, "samples: (" + samples + ")");
Gdx.app.log(LOG_TAG, "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;
}
@Override
public void onDrawFrame (GL10 gl) {
long time = System.nanoTime();
deltaTime = (time - lastFrameTime) / 1000000000.0f;
lastFrameTime = time;
Lifecycle.State prevState = lastState;
Lifecycle.State newState = currentState;
final boolean wasRunning = prevState.isAtLeast(Lifecycle.State.RESUMED);
final boolean lrunning = newState.isAtLeast(Lifecycle.State.RESUMED);
final boolean lpause = !lrunning && wasRunning;
final boolean ldestroy = newState == Lifecycle.State.DESTROYED && prevState != Lifecycle.State.DESTROYED;
final boolean lresume = !firstResume && lrunning && !wasRunning;
firstResume = false;
lastState = newState;
// After pause deltaTime can have somewhat huge value that destabilizes the mean, so let's cut it off
if (!lresume) {
mean.addValue(deltaTime);
} else {
deltaTime = 0;
}
if (lresume) {
SnapshotArray<LifecycleListener> lifecycleListeners = app.getLifecycleListeners();
synchronized (lifecycleListeners) {
LifecycleListener[] listeners = lifecycleListeners.begin();
for (int i = 0, n = lifecycleListeners.size; i < n; ++i) {
listeners[i].resume();
}
lifecycleListeners.end();
}
app.getApplicationListener().resume();
Gdx.app.log(LOG_TAG, "resumed");
}
if (lrunning) {
synchronized (app.getRunnables()) {
app.getExecutedRunnables().clear();
app.getExecutedRunnables().addAll(app.getRunnables());
app.getRunnables().clear();
}
for (int i = 0; i < app.getExecutedRunnables().size; i++) {
try {
app.getExecutedRunnables().get(i).run();
} catch (Throwable t) {
t.printStackTrace();
}
}
app.getInput().processEvents();
frameId++;
app.getApplicationListener().render();
}
if (lpause) {
SnapshotArray<LifecycleListener> lifecycleListeners = app.getLifecycleListeners();
synchronized (lifecycleListeners) {
LifecycleListener[] listeners = lifecycleListeners.begin();
for (int i = 0, n = lifecycleListeners.size; i < n; ++i) {
listeners[i].pause();
}
}
app.getApplicationListener().pause();
}
if (ldestroy) {
SnapshotArray<LifecycleListener> lifecycleListeners = app.getLifecycleListeners();
synchronized (lifecycleListeners) {
LifecycleListener[] listeners = lifecycleListeners.begin();
for (int i = 0, n = lifecycleListeners.size; i < n; ++i) {
listeners[i].dispose();
}
}
app.getApplicationListener().dispose();
Gdx.app.log(LOG_TAG, "destroyed");
}
if (time - frameStart > 1000000000) {
fps = frames;
frames = 0;
frameStart = time;
}
frames++;
}
@Override
public long getFrameId () {
return frameId;
}
/** {@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 GLVersion getGLVersion () {
return glVersion;
}
/** {@inheritDoc} */
@Override
public int getFramesPerSecond () {
return fps;
}
public void clearManagedCaches () {
Mesh.clearAllMeshes(app);
Texture.clearAllTextures(app);
Cubemap.clearAllCubemaps(app);
TextureArray.clearAllTextureArrays(app);
ShaderProgram.clearAllShaderPrograms(app);
FrameBuffer.clearAllFrameBuffers(app);
logManagedCachesStatus();
}
protected void logManagedCachesStatus () {
Gdx.app.log(LOG_TAG, Mesh.getManagedStatus());
Gdx.app.log(LOG_TAG, Texture.getManagedStatus());
Gdx.app.log(LOG_TAG, Cubemap.getManagedStatus());
Gdx.app.log(LOG_TAG, ShaderProgram.getManagedStatus());
Gdx.app.log(LOG_TAG, FrameBuffer.getManagedStatus());
}
public View getView () {
return view;
}
@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 boolean supportsDisplayModeChange () {
return false;
}
@Override
public boolean setFullscreenMode (DisplayMode displayMode) {
return false;
}
@Override
public Monitor getPrimaryMonitor () {
return new AndroidMonitor(0, 0, "Primary Monitor");
}
@Override
public Monitor getMonitor () {
return getPrimaryMonitor();
}
@Override
public Monitor[] getMonitors () {
return new Monitor[] { getPrimaryMonitor() };
}
@Override
public DisplayMode[] getDisplayModes (Monitor monitor) {
return getDisplayModes();
}
@Override
public DisplayMode getDisplayMode (Monitor monitor) {
return getDisplayMode();
}
@Override
public DisplayMode[] getDisplayModes () {
return new DisplayMode[] {getDisplayMode()};
}
@Override
public boolean setWindowedMode (int width, int height) {
return false;
}
@Override
public void setTitle (String title) {
}
@Override
public void setUndecorated (boolean undecorated) {
final int mask = (undecorated) ? 1 : 0;
app.getApplicationWindow().setFlags(LayoutParams.FLAG_FULLSCREEN, mask);
}
@Override
public void setResizable (boolean resizable) {
}
@Override
public DisplayMode getDisplayMode () {
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) {
// ignore setContinuousRendering(false) while pausing
this.isContinuous = AndroidGraphics.enforceContinuousRendering || isContinuous;
int renderMode = this.isContinuous ? GLSurfaceView.RENDERMODE_CONTINUOUSLY : GLSurfaceView.RENDERMODE_WHEN_DIRTY;
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).setRenderMode(renderMode);
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).setRenderMode(renderMode);
mean.clear();
}
}
@Override
public boolean isContinuousRendering () {
return isContinuous;
}
@Override
public void requestRendering () {
if (view != null) {
if (view instanceof GLSurfaceViewAPI18) ((GLSurfaceViewAPI18)view).requestRender();
if (view instanceof GLSurfaceView) ((GLSurfaceView)view).requestRender();
}
}
@Override
public boolean isFullscreen () {
return true;
}
@Override
public Cursor newCursor (Pixmap pixmap, int xHotspot, int yHotspot) {
return null;
}
@Override
public void setCursor (Cursor cursor) {
}
@Override
public void setSystemCursor (SystemCursor systemCursor) {
}
private static class AndroidDisplayMode extends DisplayMode {
protected AndroidDisplayMode (int width, int height, int refreshRate, int bitsPerPixel) {
super(width, height, refreshRate, bitsPerPixel);
}
}
private static class AndroidMonitor extends Monitor {
public AndroidMonitor (int virtualX, int virtualY, String name) {
super(virtualX, virtualY, name);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment