Last active
June 6, 2020 22:40
-
-
Save cypherdare/71bd7b0f98619fec61ffcfdd3ecf31af to your computer and use it in GitHub Desktop.
Attempt at fixing deadlocks by using androidx lifecycle state to abstract synchronization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* 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