• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 package org.chromium.ui;
6 
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;
13 
14 import org.chromium.base.TraceEvent;
15 
16 /**
17  * Notifies clients of the default displays's vertical sync pulses.
18  * This class works in "burst" mode: once the update is requested, the listener will be
19  * called MAX_VSYNC_COUNT times on the vertical sync pulses (on JB) or on every refresh
20  * period (on ICS, see below), unless stop() is called.
21  * On ICS, VSyncMonitor relies on setVSyncPointForICS() being called to set a reasonable
22  * approximation of a vertical sync starting point; see also http://crbug.com/156397.
23  */
24 @SuppressLint("NewApi")
25 public class VSyncMonitor {
26     private static final long NANOSECONDS_PER_SECOND = 1000000000;
27     private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
28     private static final long NANOSECONDS_PER_MICROSECOND = 1000;
29     public static final int MAX_AUTO_ONVSYNC_COUNT = 5;
30 
31     /**
32      * VSync listener class
33      */
34     public interface Listener {
35         /**
36          * Called very soon after the start of the display's vertical sync period.
37          * @param monitor The VSyncMonitor that triggered the signal.
38          * @param vsyncTimeMicros Absolute frame time in microseconds.
39          */
onVSync(VSyncMonitor monitor, long vsyncTimeMicros)40         public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros);
41     }
42 
43     private Listener mListener;
44 
45     // Display refresh rate as reported by the system.
46     private final long mRefreshPeriodNano;
47 
48     private boolean mHaveRequestInFlight;
49     private int mTriggerNextVSyncCount;
50 
51     // Choreographer is used to detect vsync on >= JB.
52     private final Choreographer mChoreographer;
53     private final Choreographer.FrameCallback mVSyncFrameCallback;
54 
55     // On ICS we just post a task through the handler (http://crbug.com/156397)
56     private final Runnable mVSyncRunnableCallback;
57     private long mGoodStartingPointNano;
58     private long mLastPostedNano;
59 
60     // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
61     // latency.
62     private final Handler mHandler = new Handler();
63     private final Runnable mSyntheticVSyncRunnable;
64     private long mLastVSyncCpuTimeNano;
65 
66     /**
67      * Constructs a VSyncMonitor
68      * @param context The application context.
69      * @param listener The listener receiving VSync notifications.
70      */
VSyncMonitor(Context context, VSyncMonitor.Listener listener)71     public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
72         this(context, listener, true);
73     }
74 
75     /**
76      * Constructs a VSyncMonitor
77      * @param context The application context.
78      * @param listener The listener receiving VSync notifications.
79      * @param enableJBVsync Whether to allow Choreographer-based notifications on JB and up.
80      */
VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync)81     public VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
82         mListener = listener;
83         float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
84                 .getDefaultDisplay().getRefreshRate();
85         if (refreshRate <= 0) refreshRate = 60;
86         mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
87         mTriggerNextVSyncCount = 0;
88 
89         if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
90             // Use Choreographer on JB+ to get notified of vsync.
91             mChoreographer = Choreographer.getInstance();
92             mVSyncFrameCallback = new Choreographer.FrameCallback() {
93                 @Override
94                 public void doFrame(long frameTimeNanos) {
95                     TraceEvent.begin("VSync");
96                     mGoodStartingPointNano = frameTimeNanos;
97                     onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
98                     TraceEvent.end("VSync");
99                 }
100             };
101             mVSyncRunnableCallback = null;
102         } else {
103             // On ICS we just hope that running tasks is relatively predictable.
104             mChoreographer = null;
105             mVSyncFrameCallback = null;
106             mVSyncRunnableCallback = new Runnable() {
107                 @Override
108                 public void run() {
109                     TraceEvent.begin("VSyncTimer");
110                     final long currentTime = getCurrentNanoTime();
111                     onVSyncCallback(currentTime, currentTime);
112                     TraceEvent.end("VSyncTimer");
113                 }
114             };
115             mLastPostedNano = 0;
116         }
117         mSyntheticVSyncRunnable = new Runnable() {
118             @Override
119             public void run() {
120                 TraceEvent.begin("VSyncSynthetic");
121                 final long currentTime = getCurrentNanoTime();
122                 onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
123                 TraceEvent.end("VSyncSynthetic");
124             }
125         };
126         mGoodStartingPointNano = getCurrentNanoTime();
127     }
128 
129     /**
130      * Returns the time interval between two consecutive vsync pulses in microseconds.
131      */
getVSyncPeriodInMicroseconds()132     public long getVSyncPeriodInMicroseconds() {
133         return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
134     }
135 
136     /**
137      * Determine whether a true vsync signal is available on this platform.
138      */
isVSyncSignalAvailable()139     private boolean isVSyncSignalAvailable() {
140         return mChoreographer != null;
141     }
142 
143     /**
144      * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered
145      * after this function is called.
146      */
stop()147     public void stop() {
148         mTriggerNextVSyncCount = 0;
149     }
150 
151     /**
152      * Request to be notified of the closest display vsync events.
153      * Listener.onVSync() will be called soon after the upcoming vsync pulses.
154      * It will be called at most MAX_AUTO_ONVSYNC_COUNT times unless requestUpdate() is called.
155      */
requestUpdate()156     public void requestUpdate() {
157         mTriggerNextVSyncCount = MAX_AUTO_ONVSYNC_COUNT;
158         postCallback();
159     }
160 
161     /**
162      * Set the best guess of the point in the past when the vsync has happened.
163      * @param goodStartingPointNano Known vsync point in the past.
164      */
setVSyncPointForICS(long goodStartingPointNano)165     public void setVSyncPointForICS(long goodStartingPointNano) {
166         mGoodStartingPointNano = goodStartingPointNano;
167     }
168 
getCurrentNanoTime()169     private long getCurrentNanoTime() {
170         return System.nanoTime();
171     }
172 
onVSyncCallback(long frameTimeNanos, long currentTimeNanos)173     private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
174         assert mHaveRequestInFlight;
175         mHaveRequestInFlight = false;
176         mLastVSyncCpuTimeNano = currentTimeNanos;
177         if (mTriggerNextVSyncCount >= 0) {
178             mTriggerNextVSyncCount--;
179             postCallback();
180         }
181         if (mListener != null) {
182             mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
183         }
184     }
185 
postCallback()186     private void postCallback() {
187         if (mHaveRequestInFlight) return;
188         mHaveRequestInFlight = true;
189         if (postSyntheticVSync()) return;
190         if (isVSyncSignalAvailable()) {
191             mChoreographer.postFrameCallback(mVSyncFrameCallback);
192         } else {
193             postRunnableCallback();
194         }
195     }
196 
postSyntheticVSync()197     private boolean postSyntheticVSync() {
198         final long currentTime = getCurrentNanoTime();
199         // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
200         // vsync is more than half a frame away.
201         if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
202         if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
203         mHandler.post(mSyntheticVSyncRunnable);
204         return true;
205     }
206 
estimateLastVSyncTime(long currentTime)207     private long estimateLastVSyncTime(long currentTime) {
208         final long lastRefreshTime = mGoodStartingPointNano +
209                 ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
210         return lastRefreshTime;
211     }
212 
postRunnableCallback()213     private void postRunnableCallback() {
214         assert !isVSyncSignalAvailable();
215         final long currentTime = getCurrentNanoTime();
216         final long lastRefreshTime = estimateLastVSyncTime(currentTime);
217         long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
218         assert delay > 0 && delay <= mRefreshPeriodNano;
219 
220         if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
221             delay += mRefreshPeriodNano;
222         }
223 
224         mLastPostedNano = currentTime + delay;
225         if (delay == 0) mHandler.post(mVSyncRunnableCallback);
226         else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);
227     }
228 }
229