1 // Copyright 2014 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.ui;
7 import android.annotation.SuppressLint;
8 import android.content.Context;
9 import android.os.Build;
10 import android.os.Handler;
11 import android.view.Choreographer;
12 import android.view.WindowManager;
14 import org.chromium.base.TraceEvent;
17 * Notifies clients of the default displays's vertical sync pulses.
18 * On ICS, VSyncMonitor relies on setVSyncPointForICS() being called to set a reasonable
19 * approximation of a vertical sync starting point; see also http://crbug.com/156397.
21 @SuppressLint("NewApi")
22 public class VSyncMonitor {
23 private static final long NANOSECONDS_PER_SECOND = 1000000000;
24 private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
25 private static final long NANOSECONDS_PER_MICROSECOND = 1000;
27 private boolean mInsideVSync = false;
29 // Conservative guess about vsync's consecutivity.
30 // If true, next tick is guaranteed to be consecutive.
31 private boolean mConsecutiveVSync = false;
34 * VSync listener class
36 public interface Listener {
38 * Called very soon after the start of the display's vertical sync period.
39 * @param monitor The VSyncMonitor that triggered the signal.
40 * @param vsyncTimeMicros Absolute frame time in microseconds.
42 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros);
45 private Listener mListener;
47 // Display refresh rate as reported by the system.
48 private long mRefreshPeriodNano;
50 private boolean mHaveRequestInFlight;
52 // Choreographer is used to detect vsync on >= JB.
53 private final Choreographer mChoreographer;
54 private final Choreographer.FrameCallback mVSyncFrameCallback;
56 // On ICS we just post a task through the handler (http://crbug.com/156397)
57 private final Runnable mVSyncRunnableCallback;
58 private long mGoodStartingPointNano;
59 private long mLastPostedNano;
61 // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
63 private final Handler mHandler = new Handler();
64 private final Runnable mSyntheticVSyncRunnable;
65 private long mLastVSyncCpuTimeNano;
68 * Constructs a VSyncMonitor
69 * @param context The application context.
70 * @param listener The listener receiving VSync notifications.
72 public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
73 this(context, listener, true);
77 * Constructs a VSyncMonitor
78 * @param context The application context.
79 * @param listener The listener receiving VSync notifications.
80 * @param enableJBVsync Whether to allow Choreographer-based notifications on JB and up.
82 public VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
84 float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
85 .getDefaultDisplay().getRefreshRate();
86 final boolean useEstimatedRefreshPeriod = refreshRate < 30;
88 if (refreshRate <= 0) refreshRate = 60;
89 mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
91 if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
92 // Use Choreographer on JB+ to get notified of vsync.
93 mChoreographer = Choreographer.getInstance();
94 mVSyncFrameCallback = new Choreographer.FrameCallback() {
96 public void doFrame(long frameTimeNanos) {
97 TraceEvent.begin("VSync");
98 if (useEstimatedRefreshPeriod && mConsecutiveVSync) {
99 // Display.getRefreshRate() is unreliable on some platforms.
100 // Adjust refresh period- initial value is based on Display.getRefreshRate()
101 // after that it asymptotically approaches the real value.
102 long lastRefreshDurationNano = frameTimeNanos - mGoodStartingPointNano;
103 float lastRefreshDurationWeight = 0.1f;
104 mRefreshPeriodNano += (long) (lastRefreshDurationWeight *
105 (lastRefreshDurationNano - mRefreshPeriodNano));
107 mGoodStartingPointNano = frameTimeNanos;
108 onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
109 TraceEvent.end("VSync");
112 mVSyncRunnableCallback = null;
114 // On ICS we just hope that running tasks is relatively predictable.
115 mChoreographer = null;
116 mVSyncFrameCallback = null;
117 mVSyncRunnableCallback = new Runnable() {
120 TraceEvent.begin("VSyncTimer");
121 final long currentTime = getCurrentNanoTime();
122 onVSyncCallback(currentTime, currentTime);
123 TraceEvent.end("VSyncTimer");
128 mSyntheticVSyncRunnable = new Runnable() {
131 TraceEvent.begin("VSyncSynthetic");
132 final long currentTime = getCurrentNanoTime();
133 onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
134 TraceEvent.end("VSyncSynthetic");
137 mGoodStartingPointNano = getCurrentNanoTime();
141 * Returns the time interval between two consecutive vsync pulses in microseconds.
143 public long getVSyncPeriodInMicroseconds() {
144 return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
148 * Determine whether a true vsync signal is available on this platform.
150 private boolean isVSyncSignalAvailable() {
151 return mChoreographer != null;
155 * Request to be notified of the closest display vsync events.
156 * Listener.onVSync() will be called soon after the upcoming vsync pulses.
158 public void requestUpdate() {
163 * Set the best guess of the point in the past when the vsync has happened.
164 * @param goodStartingPointNano Known vsync point in the past.
166 public void setVSyncPointForICS(long goodStartingPointNano) {
167 mGoodStartingPointNano = goodStartingPointNano;
171 * @return true if onVSync handler is executing. If onVSync handler
172 * introduces invalidations, View#invalidate() should be called. If
173 * View#postInvalidateOnAnimation is called instead, the corresponding onDraw
174 * will be delayed by one frame. The embedder of VSyncMonitor should check
175 * this value if it wants to post an invalidation.
177 public boolean isInsideVSync() {
181 private long getCurrentNanoTime() {
182 return System.nanoTime();
185 private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
186 assert mHaveRequestInFlight;
188 mHaveRequestInFlight = false;
189 mLastVSyncCpuTimeNano = currentTimeNanos;
191 if (mListener != null) {
192 mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
195 mInsideVSync = false;
199 private void postCallback() {
200 if (mHaveRequestInFlight) return;
201 mHaveRequestInFlight = true;
202 if (postSyntheticVSync()) return;
203 if (isVSyncSignalAvailable()) {
204 mConsecutiveVSync = mInsideVSync;
205 mChoreographer.postFrameCallback(mVSyncFrameCallback);
207 postRunnableCallback();
211 private boolean postSyntheticVSync() {
212 final long currentTime = getCurrentNanoTime();
213 // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
214 // vsync is more than half a frame away.
215 if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
216 if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
217 mHandler.post(mSyntheticVSyncRunnable);
221 private long estimateLastVSyncTime(long currentTime) {
222 final long lastRefreshTime = mGoodStartingPointNano +
223 ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
224 return lastRefreshTime;
227 private void postRunnableCallback() {
228 assert !isVSyncSignalAvailable();
229 final long currentTime = getCurrentNanoTime();
230 final long lastRefreshTime = estimateLastVSyncTime(currentTime);
231 long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
232 assert delay > 0 && delay <= mRefreshPeriodNano;
234 if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
235 delay += mRefreshPeriodNano;
238 mLastPostedNano = currentTime + delay;
239 if (delay == 0) mHandler.post(mVSyncRunnableCallback);
240 else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);