1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.os.Build;
9 import android.os.Handler;
10 import android.view.Choreographer;
11 import android.view.WindowManager;
13 import org.chromium.base.TraceEvent;
16 * Notifies clients of the default displays's vertical sync pulses.
17 * This class works in "burst" mode: once the update is requested, the listener will be
18 * called MAX_VSYNC_COUNT times on the vertical sync pulses (on JB) or on every refresh
19 * period (on ICS, see below), unless stop() is called.
20 * On ICS, VSyncMonitor relies on setVSyncPointForICS() being called to set a reasonable
21 * approximation of a vertical sync starting point; see also http://crbug.com/156397.
23 public class VSyncMonitor {
24 private static final long NANOSECONDS_PER_SECOND = 1000000000;
25 private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
26 private static final long NANOSECONDS_PER_MICROSECOND = 1000;
27 public static final int MAX_AUTO_ONVSYNC_COUNT = 5;
30 * VSync listener class
32 public interface Listener {
34 * Called very soon after the start of the display's vertical sync period.
35 * @param monitor The VSyncMonitor that triggered the signal.
36 * @param vsyncTimeMicros Absolute frame time in microseconds.
38 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros);
41 private Listener mListener;
43 // Display refresh rate as reported by the system.
44 private final long mRefreshPeriodNano;
46 private boolean mHaveRequestInFlight;
47 private int mTriggerNextVSyncCount;
49 // Choreographer is used to detect vsync on >= JB.
50 private final Choreographer mChoreographer;
51 private final Choreographer.FrameCallback mVSyncFrameCallback;
53 // On ICS we just post a task through the handler (http://crbug.com/156397)
54 private final Runnable mVSyncRunnableCallback;
55 private long mGoodStartingPointNano;
56 private long mLastPostedNano;
58 // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
60 private final Handler mHandler = new Handler();
61 private final Runnable mSyntheticVSyncRunnable;
62 private long mLastVSyncCpuTimeNano;
64 public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
65 this(context, listener, true);
68 VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
70 float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
71 .getDefaultDisplay().getRefreshRate();
72 if (refreshRate <= 0) refreshRate = 60;
73 mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
74 mTriggerNextVSyncCount = 0;
76 if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
77 // Use Choreographer on JB+ to get notified of vsync.
78 mChoreographer = Choreographer.getInstance();
79 mVSyncFrameCallback = new Choreographer.FrameCallback() {
81 public void doFrame(long frameTimeNanos) {
82 TraceEvent.begin("VSync");
83 mGoodStartingPointNano = frameTimeNanos;
84 onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
85 TraceEvent.end("VSync");
88 mVSyncRunnableCallback = null;
90 // On ICS we just hope that running tasks is relatively predictable.
91 mChoreographer = null;
92 mVSyncFrameCallback = null;
93 mVSyncRunnableCallback = new Runnable() {
96 TraceEvent.begin("VSyncTimer");
97 final long currentTime = getCurrentNanoTime();
98 onVSyncCallback(currentTime, currentTime);
99 TraceEvent.end("VSyncTimer");
104 mSyntheticVSyncRunnable = new Runnable() {
107 TraceEvent.begin("VSyncSynthetic");
108 final long currentTime = getCurrentNanoTime();
109 onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
110 TraceEvent.end("VSyncSynthetic");
113 mGoodStartingPointNano = getCurrentNanoTime();
117 * Returns the time interval between two consecutive vsync pulses in microseconds.
119 public long getVSyncPeriodInMicroseconds() {
120 return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
124 * Determine whether a true vsync signal is available on this platform.
126 public boolean isVSyncSignalAvailable() {
127 return mChoreographer != null;
131 * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered
132 * after this function is called.
135 mTriggerNextVSyncCount = 0;
139 * Unregister the listener.
140 * No vsync events will be reported afterwards.
142 public void unregisterListener() {
148 * Request to be notified of the closest display vsync events.
149 * Listener.onVSync() will be called soon after the upcoming vsync pulses.
150 * It will be called at most MAX_AUTO_ONVSYNC_COUNT times unless requestUpdate() is called.
152 public void requestUpdate() {
153 mTriggerNextVSyncCount = MAX_AUTO_ONVSYNC_COUNT;
158 * Set the best guess of the point in the past when the vsync has happened.
159 * @param goodStartingPointNano Known vsync point in the past.
161 public void setVSyncPointForICS(long goodStartingPointNano) {
162 mGoodStartingPointNano = goodStartingPointNano;
165 private long getCurrentNanoTime() {
166 return System.nanoTime();
169 private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
170 assert mHaveRequestInFlight;
171 mHaveRequestInFlight = false;
172 mLastVSyncCpuTimeNano = currentTimeNanos;
173 if (mTriggerNextVSyncCount >= 0) {
174 mTriggerNextVSyncCount--;
177 if (mListener != null) {
178 mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
182 private void postCallback() {
183 if (mHaveRequestInFlight) return;
184 mHaveRequestInFlight = true;
185 if (postSyntheticVSync()) return;
186 if (isVSyncSignalAvailable()) {
187 mChoreographer.postFrameCallback(mVSyncFrameCallback);
189 postRunnableCallback();
193 private boolean postSyntheticVSync() {
194 final long currentTime = getCurrentNanoTime();
195 // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
196 // vsync is more than half a frame away.
197 if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
198 if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
199 mHandler.post(mSyntheticVSyncRunnable);
203 private long estimateLastVSyncTime(long currentTime) {
204 final long lastRefreshTime = mGoodStartingPointNano +
205 ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
206 return lastRefreshTime;
209 private void postRunnableCallback() {
210 assert !isVSyncSignalAvailable();
211 final long currentTime = getCurrentNanoTime();
212 final long lastRefreshTime = estimateLastVSyncTime(currentTime);
213 long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
214 assert delay > 0 && delay <= mRefreshPeriodNano;
216 if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
217 delay += mRefreshPeriodNano;
220 mLastPostedNano = currentTime + delay;
221 if (delay == 0) mHandler.post(mVSyncRunnableCallback);
222 else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);