1 | // Copyright (c) 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. |
4 | |
5 | package org.chromium.content.browser; |
6 | |
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; |
12 | |
13 | import org.chromium.content.common.TraceEvent; |
14 | |
15 | /** |
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. |
22 | */ |
23 | public class VSyncMonitor { |
24 | private static final String TAG = "VSyncMonitor"; |
25 | |
26 | public interface Listener { |
27 | /** |
28 | * Called very soon after the start of the display's vertical sync period. |
29 | * @param monitor The VSyncMonitor that triggered the signal. |
30 | * @param vsyncTimeMicros Absolute frame time in microseconds. |
31 | */ |
32 | public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros); |
33 | } |
34 | |
35 | private static final long NANOSECONDS_PER_SECOND = 1000000000; |
36 | private static final long NANOSECONDS_PER_MILLISECOND = 1000000; |
37 | private static final long NANOSECONDS_PER_MICROSECOND = 1000; |
38 | |
39 | private Listener mListener; |
40 | |
41 | // Display refresh rate as reported by the system. |
42 | private final long mRefreshPeriodNano; |
43 | |
44 | // Last time requestUpdate() was called. |
45 | private long mLastUpdateRequestNano; |
46 | |
47 | private boolean mHaveRequestInFlight; |
48 | |
49 | private int mTriggerNextVSyncCount; |
50 | private static final int MAX_VSYNC_COUNT = 5; |
51 | |
52 | // Choreographer is used to detect vsync on >= JB. |
53 | private final Choreographer mChoreographer; |
54 | private final Choreographer.FrameCallback mVSyncFrameCallback; |
55 | |
56 | // On ICS we just post a task through the handler (http://crbug.com/156397) |
57 | private final Handler mHandler; |
58 | private final Runnable mVSyncRunnableCallback; |
59 | private long mGoodStartingPointNano; |
60 | private long mLastPostedNano; |
61 | |
62 | public VSyncMonitor(Context context, VSyncMonitor.Listener listener) { |
63 | this(context, listener, true); |
64 | } |
65 | |
66 | VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) { |
67 | mListener = listener; |
68 | float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) |
69 | .getDefaultDisplay().getRefreshRate(); |
70 | if (refreshRate <= 0) refreshRate = 60; |
71 | mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate); |
72 | mTriggerNextVSyncCount = 0; |
73 | |
74 | if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
75 | // Use Choreographer on JB+ to get notified of vsync. |
76 | mChoreographer = Choreographer.getInstance(); |
77 | mVSyncFrameCallback = new Choreographer.FrameCallback() { |
78 | @Override |
79 | public void doFrame(long frameTimeNanos) { |
80 | TraceEvent.instant("VSync"); |
81 | onVSyncCallback(frameTimeNanos); |
82 | } |
83 | }; |
84 | mHandler = null; |
85 | mVSyncRunnableCallback = null; |
86 | } else { |
87 | // On ICS we just hope that running tasks is relatively predictable. |
88 | mChoreographer = null; |
89 | mVSyncFrameCallback = null; |
90 | mHandler = new Handler(); |
91 | mVSyncRunnableCallback = new Runnable() { |
92 | @Override |
93 | public void run() { |
94 | TraceEvent.instant("VSyncTimer"); |
95 | onVSyncCallback(System.nanoTime()); |
96 | } |
97 | }; |
98 | mGoodStartingPointNano = getCurrentNanoTime(); |
99 | mLastPostedNano = 0; |
100 | } |
101 | } |
102 | |
103 | /** |
104 | * Returns the time interval between two consecutive vsync pulses in microseconds. |
105 | */ |
106 | public long getVSyncPeriodInMicroseconds() { |
107 | return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND; |
108 | } |
109 | |
110 | /** |
111 | * Determine whether a true vsync signal is available on this platform. |
112 | */ |
113 | public boolean isVSyncSignalAvailable() { |
114 | return mChoreographer != null; |
115 | } |
116 | |
117 | /** |
118 | * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered |
119 | * after this function is called. |
120 | */ |
121 | public void stop() { |
122 | mTriggerNextVSyncCount = 0; |
123 | } |
124 | |
125 | /** |
126 | * Unregister the listener. |
127 | * No vsync events will be reported afterwards. |
128 | */ |
129 | public void unregisterListener() { |
130 | stop(); |
131 | mListener = null; |
132 | } |
133 | |
134 | /** |
135 | * Request to be notified of the closest display vsync events. |
136 | * Listener.onVSync() will be called soon after the upcoming vsync pulses. |
137 | * It will be called at most MAX_VSYNC_COUNT times unless requestUpdate() is called again. |
138 | */ |
139 | public void requestUpdate() { |
140 | mTriggerNextVSyncCount = MAX_VSYNC_COUNT; |
141 | mLastUpdateRequestNano = getCurrentNanoTime(); |
142 | postCallback(); |
143 | } |
144 | |
145 | /** |
146 | * Set the best guess of the point in the past when the vsync has happened. |
147 | * @param goodStartingPointNano Known vsync point in the past. |
148 | */ |
149 | public void setVSyncPointForICS(long goodStartingPointNano) { |
150 | mGoodStartingPointNano = goodStartingPointNano; |
151 | } |
152 | |
153 | private long getCurrentNanoTime() { |
154 | return System.nanoTime(); |
155 | } |
156 | |
157 | private void onVSyncCallback(long frameTimeNanos) { |
158 | assert mHaveRequestInFlight; |
159 | mHaveRequestInFlight = false; |
160 | if (mTriggerNextVSyncCount > 0) { |
161 | mTriggerNextVSyncCount--; |
162 | postCallback(); |
163 | } |
164 | if (mListener != null) { |
165 | mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND); |
166 | } |
167 | } |
168 | |
169 | private void postCallback() { |
170 | if (mHaveRequestInFlight) return; |
171 | mHaveRequestInFlight = true; |
172 | if (isVSyncSignalAvailable()) { |
173 | mChoreographer.postFrameCallback(mVSyncFrameCallback); |
174 | } else { |
175 | postRunnableCallback(); |
176 | } |
177 | } |
178 | |
179 | private void postRunnableCallback() { |
180 | assert !isVSyncSignalAvailable(); |
181 | final long currentTime = mLastUpdateRequestNano; |
182 | final long lastRefreshTime = mGoodStartingPointNano + |
183 | ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano; |
184 | long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime; |
185 | assert delay >= 0 && delay < mRefreshPeriodNano; |
186 | |
187 | if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) { |
188 | delay += mRefreshPeriodNano; |
189 | } |
190 | |
191 | mLastPostedNano = currentTime + delay; |
192 | if (delay == 0) mHandler.post(mVSyncRunnableCallback); |
193 | else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND); |
194 | } |
195 | } |