// Copyright 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.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; public VSyncMonitor(Context context, VSyncMonitor.Listener listener) { this(context, listener, true); } 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. */ public 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; } /** * Unregister the listener. * No vsync events will be reported afterwards. */ public void unregisterListener() { stop(); mListener = null; } /** * 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); } }