summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/VSyncMonitor.java168
-rw-r--r--content/public/android/javatests/src/org/chromium/content/browser/VSyncMonitorTest.java168
2 files changed, 336 insertions, 0 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/VSyncMonitor.java b/content/public/android/java/src/org/chromium/content/browser/VSyncMonitor.java
new file mode 100644
index 0000000..0aca7f3
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/VSyncMonitor.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.content.browser;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Notifies clients of the default displays's vertical sync pulses.
+ */
+public class VSyncMonitor {
+ 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);
+ }
+
+ /** Time source used for test the timeout functionality. */
+ interface TestTimeSource {
+ public long currentTimeMillis();
+ }
+
+ // To save battery, we don't run vsync more than this time unless requestUpdate() is called.
+ public static final long VSYNC_TIMEOUT_MILLISECONDS = 3000;
+
+ private static final long MICROSECONDS_PER_SECOND = 1000000;
+ private static final long NANOSECONDS_PER_MICROSECOND = 1000;
+
+ private static final String TAG = VSyncMonitor.class.getName();
+
+ // Display refresh rate as reported by the system.
+ private long mRefreshPeriodMicros;
+
+ // Last time requestUpdate() was called.
+ private long mLastUpdateRequestMillis;
+
+ // Whether we are currently getting notified of vsync events.
+ private boolean mVSyncCallbackActive = false;
+
+ // Choreographer is used to detect vsync on >= JB.
+ private Choreographer mChoreographer;
+ private Choreographer.FrameCallback mFrameCallback;
+
+ private Listener mListener;
+ private View mView;
+ private VSyncTerminator mVSyncTerminator;
+ private TestTimeSource mTestTimeSource;
+
+ // VSyncTerminator keeps vsync running for a period of VSYNC_TIMEOUT_MILLISECONDS. If there is
+ // no update request during this period, the monitor will be stopped automatically.
+ private class VSyncTerminator implements Runnable {
+ public void run() {
+ if (currentTimeMillis() > mLastUpdateRequestMillis +
+ VSYNC_TIMEOUT_MILLISECONDS) {
+ stop();
+ } else if (mVSyncTerminator == this) {
+ mView.postDelayed(this, VSYNC_TIMEOUT_MILLISECONDS);
+ }
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context Resource context.
+ * @param view View to be used for timeout scheduling.
+ * @param listener Listener to be notified of vsync events.
+ */
+ public VSyncMonitor(Context context, View view, Listener listener) {
+ mListener = listener;
+ mView = view;
+
+ float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getRefreshRate();
+ if (refreshRate <= 0) {
+ refreshRate = 60;
+ }
+ mRefreshPeriodMicros = (long) (MICROSECONDS_PER_SECOND / refreshRate);
+
+ // Use Choreographer on JB+ to get notified of vsync.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mChoreographer = Choreographer.getInstance();
+ mFrameCallback = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ postVSyncCallback();
+ mListener.onVSync(VSyncMonitor.this,
+ frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
+ }
+ };
+ }
+ }
+
+ /**
+ * Returns the time interval between two consecutive vsync pulses in microseconds.
+ */
+ public long getVSyncPeriodInMicroseconds() {
+ return mRefreshPeriodMicros;
+ }
+
+ /**
+ * Determine whether a true vsync signal is available on this platform.
+ */
+ public boolean isVSyncSignalAvailable() {
+ return mChoreographer != null;
+ }
+
+ /**
+ * Request to be notified of display vsync events. Listener.onVSync() will be called soon after
+ * the upcoming vsync pulses. If VSYNC_TIMEOUT_MILLISECONDS passes after the last time this
+ * function is called, the updates will cease automatically.
+ *
+ * This function throws an IllegalStateException if isVSyncSignalAvailable() returns false.
+ */
+ public void requestUpdate() {
+ if (!isVSyncSignalAvailable()) {
+ throw new IllegalStateException("VSync signal not available");
+ }
+ mLastUpdateRequestMillis = currentTimeMillis();
+ if (!mVSyncCallbackActive) {
+ mVSyncCallbackActive = true;
+ mVSyncTerminator = new VSyncTerminator();
+ mView.postDelayed(mVSyncTerminator, VSYNC_TIMEOUT_MILLISECONDS);
+ postVSyncCallback();
+ }
+ }
+
+ private void postVSyncCallback() {
+ if (!mVSyncCallbackActive) {
+ return;
+ }
+ mChoreographer.postFrameCallback(mFrameCallback);
+ }
+
+ /**
+ * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered
+ * after this function is called.
+ */
+ public void stop() {
+ mVSyncCallbackActive = false;
+ mVSyncTerminator = null;
+ }
+
+ public boolean isActive() {
+ return mVSyncCallbackActive;
+ }
+
+ void setTestDependencies(View testView, TestTimeSource testTimeSource) {
+ mView = testView;
+ mTestTimeSource = testTimeSource;
+ }
+
+ private long currentTimeMillis() {
+ if (mTestTimeSource != null) {
+ return mTestTimeSource.currentTimeMillis();
+ }
+ return System.currentTimeMillis();
+ }
+}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/VSyncMonitorTest.java b/content/public/android/javatests/src/org/chromium/content/browser/VSyncMonitorTest.java
new file mode 100644
index 0000000..730a874
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/VSyncMonitorTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.content.browser;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.browser.VSyncMonitor;
+
+public class VSyncMonitorTest extends InstrumentationTestCase {
+ private class VSyncDataCollector implements VSyncMonitor.Listener {
+ public long framePeriods[];
+ public int frameCount;
+
+ private long previousVSyncTimeMicros;
+
+ VSyncDataCollector(int frames) {
+ framePeriods = new long[frames];
+ }
+
+ @Override
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
+ if (previousVSyncTimeMicros == 0) {
+ previousVSyncTimeMicros = vsyncTimeMicros;
+ return;
+ }
+ if (frameCount >= framePeriods.length) {
+ synchronized (this) {
+ notify();
+ }
+ return;
+ }
+ framePeriods[frameCount++] = vsyncTimeMicros - previousVSyncTimeMicros;
+ previousVSyncTimeMicros = vsyncTimeMicros;
+ monitor.requestUpdate();
+ }
+ };
+
+ // The vsync monitor must be created on the UI thread to avoid associating the underlying
+ // Choreographer with the Looper from the test runner thread.
+ private VSyncMonitor createVSyncMonitor(final VSyncMonitor.Listener listener) {
+ return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<VSyncMonitor>() {
+ @Override
+ public VSyncMonitor call() {
+ Context context = getInstrumentation().getContext();
+ View dummyView = new View(context);
+ return new VSyncMonitor(context, dummyView, listener);
+ }
+ });
+ }
+
+ // Check that the vsync period roughly matches the timestamps that the monitor generates.
+ @MediumTest
+ public void testVSyncPeriod() throws InterruptedException {
+ // Collect roughly two seconds of data on a 60 fps display.
+ VSyncDataCollector collector = new VSyncDataCollector(60 * 2);
+ VSyncMonitor monitor = createVSyncMonitor(collector);
+
+ long reportedFramePeriod = monitor.getVSyncPeriodInMicroseconds();
+ assertTrue(reportedFramePeriod > 0);
+
+ if (!monitor.isVSyncSignalAvailable()) {
+ // Cannot do timing test without real vsync signal. We can still verify that the monitor
+ // throws if we try to use it.
+ try {
+ monitor.requestUpdate();
+ fail("IllegalStateException not thrown.");
+ } catch (IllegalStateException e) {
+ }
+ return;
+ }
+
+ monitor.requestUpdate();
+ synchronized (collector) {
+ collector.wait();
+ }
+ monitor.stop();
+
+ // Check that the median frame rate is within 10% of the reported frame period.
+ Arrays.sort(collector.framePeriods, 0, collector.framePeriods.length);
+ long medianFramePeriod = collector.framePeriods[collector.framePeriods.length / 2];
+ if (Math.abs(medianFramePeriod - reportedFramePeriod) > reportedFramePeriod * .1) {
+ fail("Measured median frame period " + medianFramePeriod +
+ " differs by more than 10% from the reported frame period " +
+ reportedFramePeriod);
+ }
+ }
+
+ private class TestView extends View {
+ private Runnable mPendingAction;
+
+ public TestView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean postDelayed(Runnable action, long delayMillis) {
+ mPendingAction = action;
+ return true;
+ }
+
+ public void runPendingAction() {
+ assertNotNull(mPendingAction);
+ mPendingAction.run();
+ }
+ };
+
+ private class TestTimeSource implements VSyncMonitor.TestTimeSource {
+ long mCurrentTimeMillis;
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTimeMillis;
+ }
+
+ void setCurrentTimeMillis(long currentTimeMillis) {
+ mCurrentTimeMillis = currentTimeMillis;
+ }
+ };
+
+ // Check the the vsync monitor terminates after we have stopped requesting updates for some
+ // time.
+ @MediumTest
+ public void testVSyncTimeout() throws InterruptedException {
+ VSyncMonitor.Listener listener = new VSyncMonitor.Listener() {
+ @Override
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
+ // No-op vsync listener.
+ }
+ };
+ VSyncMonitor monitor = createVSyncMonitor(listener);
+ if (!monitor.isVSyncSignalAvailable()) {
+ // Cannot do timeout test without real vsync signal.
+ return;
+ }
+
+ TestTimeSource testTimeSource = new TestTimeSource();
+ TestView testView = new TestView(getInstrumentation().getContext());
+ monitor.setTestDependencies(testView, testTimeSource);
+
+ // Requesting an update should make the monitor active.
+ testTimeSource.setCurrentTimeMillis(0);
+ monitor.requestUpdate();
+ assertTrue(monitor.isActive());
+
+ // Running the timeout callback should have no effect when no time has passed.
+ testView.runPendingAction();
+ assertTrue(monitor.isActive());
+
+ // Ditto after half the timeout interval.
+ testTimeSource.setCurrentTimeMillis(VSyncMonitor.VSYNC_TIMEOUT_MILLISECONDS / 2);
+ testView.runPendingAction();
+ assertTrue(monitor.isActive());
+
+ // The monitor should be stopped after the timeout interval has passed.
+ testTimeSource.setCurrentTimeMillis(VSyncMonitor.VSYNC_TIMEOUT_MILLISECONDS + 1);
+ testView.runPendingAction();
+ assertFalse(monitor.isActive());
+ }
+}