Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: refactor frame loop to use vsync #2142

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ of this software and associated documentation files (the "Software"), to deal
import android.os.PowerManager;
import android.preference.PreferenceManager.OnActivityResultListener;
import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
Expand Down Expand Up @@ -229,7 +230,6 @@ private void resume() {
mGLSurfaceView.onResume();
rendererPaused = false;
}
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}

private void resumeIfHasFocus() {
Expand All @@ -248,7 +248,6 @@ protected void onPause() {
paused = true;
super.onPause();
AxmolEngine.onPause();
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}

@Override
Expand Down Expand Up @@ -324,7 +323,9 @@ public void init() {
//if (isAndroidEmulator())
// this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);

this.mGLSurfaceView.setRenderer(new AxmolRenderer());
Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
this.mGLSurfaceView.setRenderer(new AxmolRenderer(display));
this.mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
this.mGLSurfaceView.setEditText(edittext);

// Set framelayout as the content view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,83 @@ of this software and associated documentation files (the "Software"), to deal
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Choreographer;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;

import java.util.concurrent.CountDownLatch;


// This runs a thread that notifies AxmolGLSurfaceView about vsync events, so that it can request
// rendering a new frame from the GL thread if required.
//
// Choreographer notifies about vsync events using the Looper running in current thread. We could
// use the Choreographer/Looper in UI thread, but it means that if UI thread is busy for some
// reason, then vsync notifications would be delayed. To avoid this we create a separate thread,
// that handles only vsync events.
class VsyncNotifierThread extends Thread implements Choreographer.FrameCallback {
private AxmolGLSurfaceView mSurfaceView;
private Choreographer mChoreographer;
private CountDownLatch mStartSignal = new CountDownLatch(1);

static VsyncNotifierThread sThread;

private VsyncNotifierThread() {}

// AxmolGLSurfaceView uses this to subscribe or unsubscribe from vsync events.
public synchronized void setSubscriber(AxmolGLSurfaceView subscriber) {
mSurfaceView = subscriber;
if (subscriber == null)
mChoreographer.removeFrameCallback(this);
else
mChoreographer.postFrameCallback(this);
}

// Choreographer calls this on vsync event.
@Override
public synchronized void doFrame(long frameTimeNanos) {
if (mSurfaceView != null) {
mSurfaceView.onVsync(frameTimeNanos);
mChoreographer.postFrameCallback(this);
}
}

@Override
public void run() {
Looper.prepare();

mChoreographer = Choreographer.getInstance();
mStartSignal.countDown();

Looper.loop();
}

public static VsyncNotifierThread getInstance() {
if (sThread == null) {
sThread = new VsyncNotifierThread();
sThread.setName("Axmol VsyncNotifierThread");
sThread.start();
// Make sure the thread has started, because the later code most likely will call
// setSubscriber() which will try to access mChoreographer, that is initialized during
// thread start.
while (sThread.mStartSignal.getCount() > 0) {
try {
sThread.mStartSignal.await();
} catch (InterruptedException e) {
}
}
}
return sThread;
}
}


public class AxmolGLSurfaceView extends GLSurfaceView {
// ===========================================================
// Constants
Expand Down Expand Up @@ -193,10 +262,12 @@ public void run() {
AxmolGLSurfaceView.this.mRenderer.handleOnResume();
}
});
VsyncNotifierThread.getInstance().setSubscriber(this);
}

@Override
public void onPause() {
VsyncNotifierThread.getInstance().setSubscriber(null);
this.queueEvent(new Runnable() {
@Override
public void run() {
Expand All @@ -206,6 +277,11 @@ public void run() {
super.onPause();
}

public void onVsync(long frameTimeNanos) {
if (mRenderer.onVsync(frameTimeNanos))
requestRender();
}

@Override
public boolean onTouchEvent(final MotionEvent pMotionEvent) {
// these data are used in ACTION_MOVE and ACTION_CANCEL
Expand Down
64 changes: 35 additions & 29 deletions core/platform/android/java/src/org/axmol/lib/AxmolRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ of this software and associated documentation files (the "Software"), to deal
package org.axmol.lib;

import android.opengl.GLSurfaceView;
import android.view.Display;

import java.util.concurrent.atomic.AtomicLong;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
Expand All @@ -34,16 +37,19 @@ public class AxmolRenderer implements GLSurfaceView.Renderer {
// ===========================================================

private final static long NANOSECONDSPERSECOND = 1000000000L;
private final static long NANOSECONDSPERMICROSECOND = 1000000L;
private final static long NANOSECONDSPERMILLISECOND = 1000000L;

// The final animation interval which is used in 'onDrawFrame'
private static long sAnimationInterval = (long) (1.0f / 60f * AxmolRenderer.NANOSECONDSPERSECOND);
// Reference interval between render requests.
private static final AtomicLong sAnimationInterval = new AtomicLong(AxmolRenderer.NANOSECONDSPERSECOND / 60);

// ===========================================================
// Fields
// ===========================================================

private long mLastTickInNanoSeconds;
private long mNextFrameStartTime = 0; // Note: read and written by VsyncNotifierThread's thread
private long mVsyncInterval;
private final AtomicLong mCurrentFrameStartTime = new AtomicLong();

private int mScreenWidth;
private int mScreenHeight;
private boolean mNativeInitCompleted = false;
Expand All @@ -53,12 +59,17 @@ public class AxmolRenderer implements GLSurfaceView.Renderer {
// Constructors
// ===========================================================

public AxmolRenderer(Display display)
{
mVsyncInterval = (long)((double)NANOSECONDSPERSECOND / (double)display.getRefreshRate());
}

// ===========================================================
// Getter & Setter
// ===========================================================

public static void setAnimationInterval(float interval) {
sAnimationInterval = (long) (interval * AxmolRenderer.NANOSECONDSPERSECOND);
sAnimationInterval.set((long)((double)interval * (double)AxmolRenderer.NANOSECONDSPERSECOND));
}

public void setScreenWidthAndHeight(final int surfaceWidth, final int surfaceHeight) {
Expand All @@ -73,7 +84,7 @@ public void setScreenWidthAndHeight(final int surfaceWidth, final int surfaceHei
@Override
public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
AxmolRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
AxmolRenderer.nativeInitEglPresentationTime();

if (mNativeInitCompleted) {
// This must be from an OpenGL context loss
Expand All @@ -88,30 +99,24 @@ public void onSurfaceChanged(final GL10 GL10, final int width, final int height)
AxmolRenderer.nativeOnSurfaceChanged(width, height);
}

// Determines if frame rendering should be requested.
// Note: this is called in VsyncNotifierThread's thread via AxmolGLSurfaceView.
public boolean onVsync(long frameTimeNanos) {
if (frameTimeNanos >= mNextFrameStartTime) {
long threshold = NANOSECONDSPERMILLISECOND;
mNextFrameStartTime = frameTimeNanos + sAnimationInterval.get() - threshold;
mCurrentFrameStartTime.set(frameTimeNanos);
return true;
}

return false;
}

@Override
public void onDrawFrame(final GL10 gl) {
/*
* Fix 60fps limiting doesn't work when high-end device is working in 120fps mode.
*/
if (AxmolRenderer.sAnimationInterval <= 1.0f / 1200.0f * AxmolRenderer.NANOSECONDSPERSECOND) {
AxmolRenderer.nativeRender();
} else {
final long now = System.nanoTime();
final long interval = now - this.mLastTickInNanoSeconds;

/*
* Render time MUST be counted in, or the FPS will slower than appointed.
*/
this.mLastTickInNanoSeconds = now;
AxmolRenderer.nativeRender();

if (interval < AxmolRenderer.sAnimationInterval) {
try {
Thread.sleep((AxmolRenderer.sAnimationInterval - interval) / AxmolRenderer.NANOSECONDSPERMICROSECOND);
} catch (final Exception e) {
}
}
}
long frameStartTime = mCurrentFrameStartTime.get();
long framePresentationTime = frameStartTime + 2 * mVsyncInterval;
AxmolRenderer.nativeRender(framePresentationTime);
}

// ===========================================================
Expand All @@ -123,7 +128,8 @@ public void onDrawFrame(final GL10 gl) {
private static native void nativeTouchesMove(final int[] ids, final float[] xs, final float[] ys);
private static native void nativeTouchesCancel(final int[] ids, final float[] xs, final float[] ys);
private static native boolean nativeKeyEvent(final int keyCode,boolean isPressed);
private static native void nativeRender();
private static native void nativeInitEglPresentationTime();
private static native void nativeRender(final long framePresentationTimeNanos);
private static native void nativeInit(final int width, final int height);
private static native void nativeOnContextLost();
private static native void nativeOnSurfaceChanged(final int width, final int height);
Expand Down
30 changes: 29 additions & 1 deletion core/platform/android/jni/Java_org_axmol_lib_AxmolRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,44 @@
#include "platform/Application.h"
#include "platform/FileUtils.h"
#include <jni.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>

#include "base/UTF8.h"

using namespace ax;

extern "C" {

JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolRenderer_nativeRender(JNIEnv*, jclass)

static EGLDisplay gEglDisplay = EGL_NO_DISPLAY;
static EGLSurface gEglSurface = EGL_NO_SURFACE;
static PFNEGLPRESENTATIONTIMEANDROIDPROC gEglPresentationTime = nullptr;


JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolRenderer_nativeInitEglPresentationTime(JNIEnv*, jclass)
{
gEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
gEglSurface = eglGetCurrentSurface(EGL_DRAW);
gEglPresentationTime = nullptr;

if (gEglDisplay != EGL_NO_SURFACE && gEglSurface != EGL_NO_SURFACE)
{
auto extensions = eglQueryString(gEglDisplay, EGL_EXTENSIONS);
if (strstr(extensions, "EGL_ANDROID_presentation_time"))
{
gEglPresentationTime = reinterpret_cast<PFNEGLPRESENTATIONTIMEANDROIDPROC>(
eglGetProcAddress("eglPresentationTimeANDROID"));
}
}
}

JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolRenderer_nativeRender(JNIEnv*, jclass, jlong framePresentationTimeNanos)
{
ax::Director::getInstance()->mainLoop();

if (gEglPresentationTime != nullptr)
gEglPresentationTime(gEglDisplay, gEglSurface, EGLnsecsANDROID(framePresentationTimeNanos));
}

JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolRenderer_nativeOnPause(JNIEnv*, jclass)
Expand Down
4 changes: 3 additions & 1 deletion tests/cpp-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ list(APPEND GAME_HEADER
Source/TouchesTest/Paddle.h
Source/ActionManagerTest/ActionManagerTest.h
Source/ClickAndMoveTest/ClickAndMoveTest.h
Source/MainLoopTest/MainLoopTest.h
Source/MaterialSystemTest/MaterialSystemTest.h
Source/IntervalTest/IntervalTest.h
Source/TileMapTest/TileMapTest2.h
Expand Down Expand Up @@ -302,6 +303,7 @@ list(APPEND GAME_SOURCE
Source/LayerTest/LayerTest.cpp
Source/LightTest/LightTest.cpp
Source/MaterialSystemTest/MaterialSystemTest.cpp
Source/MainLoopTest/MainLoopTest.cpp
Source/MenuTest/MenuTest.cpp
Source/MotionStreakTest/MotionStreakTest.cpp
Source/MultiTouchTest/MultiTouchTest.cpp
Expand Down Expand Up @@ -608,7 +610,7 @@ endif()

if((NOT IOS) AND (NOT WINRT))
message("CMake ${APP_NAME} target_precompile_headers")
target_precompile_headers(${APP_NAME} PRIVATE
target_precompile_headers(${APP_NAME} PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:axmol.h>"
)
endif()
Expand Down
Loading