summaryrefslogtreecommitdiffstats
path: root/ui/android
diff options
context:
space:
mode:
authorsievers@chromium.org <sievers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-29 23:40:32 +0000
committersievers@chromium.org <sievers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-29 23:40:32 +0000
commitf22e4584788e38f3b4b8b03981ac308f83cb3fc0 (patch)
treed970d3b1ea42a4feaec5e5e50eb7ba71c5763ef5 /ui/android
parent6daa739b3beaed1686d73da9d55ec8e8d54b9d49 (diff)
downloadchromium_src-f22e4584788e38f3b4b8b03981ac308f83cb3fc0.zip
chromium_src-f22e4584788e38f3b4b8b03981ac308f83cb3fc0.tar.gz
chromium_src-f22e4584788e38f3b4b8b03981ac308f83cb3fc0.tar.bz2
Android: Consolidate and simplify VSync logic
This moves handling of VSync over to WindowAndroid. It can have one client to receive vsync signals for the main browser-side scheduling logic. It also forwards the vsync to the WindowAndroidObservers, i.e. RenderWidgetHostView where it's used for sending BeginFrame signals to the renderer. This prepares for consolidating the scheduling logic in a single place upstream. Which will then further allow us to use the cc scheduler (with SingleThreadProxy) and unblock the landing of https://codereview.chromium.org/134623005/. BUG=234173,363479 NOTRY=True Review URL: https://codereview.chromium.org/236193013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266995 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/android')
-rw-r--r--ui/android/java/src/org/chromium/ui/VSyncMonitor.java226
-rw-r--r--ui/android/java/src/org/chromium/ui/base/WindowAndroid.java53
2 files changed, 276 insertions, 3 deletions
diff --git a/ui/android/java/src/org/chromium/ui/VSyncMonitor.java b/ui/android/java/src/org/chromium/ui/VSyncMonitor.java
new file mode 100644
index 0000000..527b831
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/VSyncMonitor.java
@@ -0,0 +1,226 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.view.Choreographer;
+import android.view.WindowManager;
+
+import org.chromium.base.TraceEvent;
+
+/**
+ * Notifies clients of the default displays's vertical sync pulses.
+ * This class works in "burst" mode: once the update is requested, the listener will be
+ * called MAX_VSYNC_COUNT times on the vertical sync pulses (on JB) or on every refresh
+ * period (on ICS, see below), unless stop() is called.
+ * On ICS, VSyncMonitor relies on setVSyncPointForICS() being called to set a reasonable
+ * approximation of a vertical sync starting point; see also http://crbug.com/156397.
+ */
+public class VSyncMonitor {
+ private static final long NANOSECONDS_PER_SECOND = 1000000000;
+ private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
+ private static final long NANOSECONDS_PER_MICROSECOND = 1000;
+ public static final int MAX_AUTO_ONVSYNC_COUNT = 5;
+
+ /**
+ * VSync listener class
+ */
+ public interface Listener {
+ /**
+ * Called very soon after the start of the display's vertical sync period.
+ * @param monitor The VSyncMonitor that triggered the signal.
+ * @param vsyncTimeMicros Absolute frame time in microseconds.
+ */
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros);
+ }
+
+ private Listener mListener;
+
+ // Display refresh rate as reported by the system.
+ private final long mRefreshPeriodNano;
+
+ private boolean mHaveRequestInFlight;
+ private int mTriggerNextVSyncCount;
+
+ // Choreographer is used to detect vsync on >= JB.
+ private final Choreographer mChoreographer;
+ private final Choreographer.FrameCallback mVSyncFrameCallback;
+
+ // On ICS we just post a task through the handler (http://crbug.com/156397)
+ private final Runnable mVSyncRunnableCallback;
+ private long mGoodStartingPointNano;
+ private long mLastPostedNano;
+
+ // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
+ // latency.
+ private final Handler mHandler = new Handler();
+ private final Runnable mSyntheticVSyncRunnable;
+ private long mLastVSyncCpuTimeNano;
+
+ /**
+ * Constructs a VSyncMonitor
+ * @param context The application context.
+ * @param listener The listener receiving VSync notifications.
+ */
+ public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
+ this(context, listener, true);
+ }
+
+ /**
+ * Constructs a VSyncMonitor
+ * @param context The application context.
+ * @param listener The listener receiving VSync notifications.
+ * @param enableJBVsync Whether to allow Choreographer-based notifications on JB and up.
+ */
+ public VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
+ mListener = listener;
+ float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getRefreshRate();
+ if (refreshRate <= 0) refreshRate = 60;
+ mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
+ mTriggerNextVSyncCount = 0;
+
+ if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ // Use Choreographer on JB+ to get notified of vsync.
+ mChoreographer = Choreographer.getInstance();
+ mVSyncFrameCallback = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ TraceEvent.begin("VSync");
+ mGoodStartingPointNano = frameTimeNanos;
+ onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
+ TraceEvent.end("VSync");
+ }
+ };
+ mVSyncRunnableCallback = null;
+ } else {
+ // On ICS we just hope that running tasks is relatively predictable.
+ mChoreographer = null;
+ mVSyncFrameCallback = null;
+ mVSyncRunnableCallback = new Runnable() {
+ @Override
+ public void run() {
+ TraceEvent.begin("VSyncTimer");
+ final long currentTime = getCurrentNanoTime();
+ onVSyncCallback(currentTime, currentTime);
+ TraceEvent.end("VSyncTimer");
+ }
+ };
+ mLastPostedNano = 0;
+ }
+ mSyntheticVSyncRunnable = new Runnable() {
+ @Override
+ public void run() {
+ TraceEvent.begin("VSyncSynthetic");
+ final long currentTime = getCurrentNanoTime();
+ onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
+ TraceEvent.end("VSyncSynthetic");
+ }
+ };
+ mGoodStartingPointNano = getCurrentNanoTime();
+ }
+
+ /**
+ * Returns the time interval between two consecutive vsync pulses in microseconds.
+ */
+ public long getVSyncPeriodInMicroseconds() {
+ return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
+ }
+
+ /**
+ * Determine whether a true vsync signal is available on this platform.
+ */
+ private boolean isVSyncSignalAvailable() {
+ return mChoreographer != null;
+ }
+
+ /**
+ * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered
+ * after this function is called.
+ */
+ public void stop() {
+ mTriggerNextVSyncCount = 0;
+ }
+
+ /**
+ * Request to be notified of the closest display vsync events.
+ * Listener.onVSync() will be called soon after the upcoming vsync pulses.
+ * It will be called at most MAX_AUTO_ONVSYNC_COUNT times unless requestUpdate() is called.
+ */
+ public void requestUpdate() {
+ mTriggerNextVSyncCount = MAX_AUTO_ONVSYNC_COUNT;
+ postCallback();
+ }
+
+ /**
+ * Set the best guess of the point in the past when the vsync has happened.
+ * @param goodStartingPointNano Known vsync point in the past.
+ */
+ public void setVSyncPointForICS(long goodStartingPointNano) {
+ mGoodStartingPointNano = goodStartingPointNano;
+ }
+
+ private long getCurrentNanoTime() {
+ return System.nanoTime();
+ }
+
+ private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
+ assert mHaveRequestInFlight;
+ mHaveRequestInFlight = false;
+ mLastVSyncCpuTimeNano = currentTimeNanos;
+ if (mTriggerNextVSyncCount >= 0) {
+ mTriggerNextVSyncCount--;
+ postCallback();
+ }
+ if (mListener != null) {
+ mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
+ }
+ }
+
+ private void postCallback() {
+ if (mHaveRequestInFlight) return;
+ mHaveRequestInFlight = true;
+ if (postSyntheticVSync()) return;
+ if (isVSyncSignalAvailable()) {
+ mChoreographer.postFrameCallback(mVSyncFrameCallback);
+ } else {
+ postRunnableCallback();
+ }
+ }
+
+ private boolean postSyntheticVSync() {
+ final long currentTime = getCurrentNanoTime();
+ // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
+ // vsync is more than half a frame away.
+ if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
+ if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
+ mHandler.post(mSyntheticVSyncRunnable);
+ return true;
+ }
+
+ private long estimateLastVSyncTime(long currentTime) {
+ final long lastRefreshTime = mGoodStartingPointNano +
+ ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
+ return lastRefreshTime;
+ }
+
+ private void postRunnableCallback() {
+ assert !isVSyncSignalAvailable();
+ final long currentTime = getCurrentNanoTime();
+ final long lastRefreshTime = estimateLastVSyncTime(currentTime);
+ long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
+ assert delay > 0 && delay <= mRefreshPeriodNano;
+
+ if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
+ delay += mRefreshPeriodNano;
+ }
+
+ mLastPostedNano = currentTime + delay;
+ if (delay == 0) mHandler.post(mVSyncRunnableCallback);
+ else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
index 309b735..8be2dd0 100644
--- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -17,6 +17,7 @@ import android.widget.Toast;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
+import org.chromium.ui.VSyncMonitor;
import java.lang.ref.WeakReference;
import java.util.HashMap;
@@ -30,6 +31,8 @@ public class WindowAndroid {
// Native pointer to the c++ WindowAndroid object.
private long mNativeWindowAndroid = 0;
+ private final VSyncMonitor mVSyncMonitor;
+ private VSyncClient mVSyncClient = null;
// A string used as a key to store intent errors in a bundle
static final String WINDOW_CALLBACK_ERRORS = "window_callback_errors";
@@ -45,6 +48,18 @@ public class WindowAndroid {
// the Android lint warning "UseSparseArrays".
protected HashMap<Integer, String> mIntentErrors;
+ private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() {
+ @Override
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
+ if (mVSyncClient != null) {
+ mVSyncClient.onVSync(vsyncTimeMicros);
+ }
+ if (mNativeWindowAndroid != 0) {
+ nativeOnVSync(mNativeWindowAndroid, vsyncTimeMicros);
+ }
+ }
+ };
+
/**
* @param context The application context.
*/
@@ -54,6 +69,7 @@ public class WindowAndroid {
mApplicationContext = context;
mOutstandingIntents = new SparseArray<IntentCallback>();
mIntentErrors = new HashMap<Integer, String>();
+ mVSyncMonitor = new VSyncMonitor(context, mVSyncListener);
}
/**
@@ -216,6 +232,36 @@ public class WindowAndroid {
}
/**
+ * An interface to receive VSync notifications from the window.
+ * The one and only client is set with setVSyncClient(client).
+ */
+ public interface VSyncClient {
+ /**
+ * Called very soon after the start of the display's vertical sync period.
+ * @param vsyncTimeMicros Absolute frame time in microseconds.
+ */
+ void onVSync(long vsyncTimeMicros);
+ }
+
+ /**
+ * Sets the VSyncClient.
+ * @param client The client receiving VSync notifications.
+ */
+ public void setVSyncClient(VSyncClient client) {
+ assert mVSyncClient == null || client == null;
+ mVSyncClient = client;
+ }
+
+ /**
+ * Request a VSync callback.
+ * VSyncClient.onVSync() will be called at least once.
+ */
+ @CalledByNative
+ public void requestVSyncUpdate() {
+ mVSyncMonitor.requestUpdate();
+ }
+
+ /**
* An interface that intent callback objects have to implement.
*/
public interface IntentCallback {
@@ -226,7 +272,7 @@ public class WindowAndroid {
* @param contentResolver An instance of ContentResolver class for accessing returned data.
* @param data The data returned by the intent.
*/
- public void onIntentCompleted(WindowAndroid window, int resultCode,
+ void onIntentCompleted(WindowAndroid window, int resultCode,
ContentResolver contentResolver, Intent data);
}
@@ -257,7 +303,7 @@ public class WindowAndroid {
*/
public long getNativePointer() {
if (mNativeWindowAndroid == 0) {
- mNativeWindowAndroid = nativeInit();
+ mNativeWindowAndroid = nativeInit(mVSyncMonitor.getVSyncPeriodInMicroseconds());
}
return mNativeWindowAndroid;
}
@@ -271,7 +317,8 @@ public class WindowAndroid {
return null;
}
- private native long nativeInit();
+ private native long nativeInit(long vsyncPeriod);
+ private native void nativeOnVSync(long nativeWindowAndroid, long vsyncTimeMicros);
private native void nativeDestroy(long nativeWindowAndroid);
}