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;
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;
48 // Choreographer is used to detect vsync on >= JB.
49 private final Choreographer mChoreographer;
50 private final Choreographer.FrameCallback mVSyncFrameCallback;
52 // On ICS we just post a task through the handler (http://crbug.com/156397)
53 private final Runnable mVSyncRunnableCallback;
54 private long mGoodStartingPointNano;
55 private long mLastPostedNano;
57 // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
59 private final Handler mHandler = new Handler();
60 private final Runnable mSyntheticVSyncRunnable;
61 private long mLastVSyncCpuTimeNano;
64 * Constructs a VSyncMonitor
65 * @param context The application context.
66 * @param listener The listener receiving VSync notifications.
68 public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
69 this(context, listener, true);
73 * Constructs a VSyncMonitor
74 * @param context The application context.
75 * @param listener The listener receiving VSync notifications.
76 * @param enableJBVsync Whether to allow Choreographer-based notifications on JB and up.
78 public VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
80 float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
81 .getDefaultDisplay().getRefreshRate();
82 if (refreshRate <= 0) refreshRate = 60;
83 mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
85 if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
86 // Use Choreographer on JB+ to get notified of vsync.
87 mChoreographer = Choreographer.getInstance();
88 mVSyncFrameCallback = new Choreographer.FrameCallback() {
90 public void doFrame(long frameTimeNanos) {
91 TraceEvent.begin("VSync");
92 mGoodStartingPointNano = frameTimeNanos;
93 onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
94 TraceEvent.end("VSync");
97 mVSyncRunnableCallback = null;
99 // On ICS we just hope that running tasks is relatively predictable.
100 mChoreographer = null;
101 mVSyncFrameCallback = null;
102 mVSyncRunnableCallback = new Runnable() {
105 TraceEvent.begin("VSyncTimer");
106 final long currentTime = getCurrentNanoTime();
107 onVSyncCallback(currentTime, currentTime);
108 TraceEvent.end("VSyncTimer");
113 mSyntheticVSyncRunnable = new Runnable() {
116 TraceEvent.begin("VSyncSynthetic");
117 final long currentTime = getCurrentNanoTime();
118 onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
119 TraceEvent.end("VSyncSynthetic");
122 mGoodStartingPointNano = getCurrentNanoTime();
126 * Returns the time interval between two consecutive vsync pulses in microseconds.
128 public long getVSyncPeriodInMicroseconds() {
129 return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
133 * Determine whether a true vsync signal is available on this platform.
135 private boolean isVSyncSignalAvailable() {
136 return mChoreographer != null;
140 * Request to be notified of the closest display vsync events.
141 * Listener.onVSync() will be called soon after the upcoming vsync pulses.
143 public void requestUpdate() {
148 * Set the best guess of the point in the past when the vsync has happened.
149 * @param goodStartingPointNano Known vsync point in the past.
151 public void setVSyncPointForICS(long goodStartingPointNano) {
152 mGoodStartingPointNano = goodStartingPointNano;
156 * @return true if onVSync handler is executing. If onVSync handler
157 * introduces invalidations, View#invalidate() should be called. If
158 * View#postInvalidateOnAnimation is called instead, the corresponding onDraw
159 * will be delayed by one frame. The embedder of VSyncMonitor should check
160 * this value if it wants to post an invalidation.
162 public boolean isInsideVSync() {
166 private long getCurrentNanoTime() {
167 return System.nanoTime();
170 private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
171 assert mHaveRequestInFlight;
173 mHaveRequestInFlight = false;
174 mLastVSyncCpuTimeNano = currentTimeNanos;
176 if (mListener != null) {
177 mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
180 mInsideVSync = false;
184 private void postCallback() {
185 if (mHaveRequestInFlight) return;
186 mHaveRequestInFlight = true;
187 if (postSyntheticVSync()) return;
188 if (isVSyncSignalAvailable()) {
189 mChoreographer.postFrameCallback(mVSyncFrameCallback);
191 postRunnableCallback();
195 private boolean postSyntheticVSync() {
196 final long currentTime = getCurrentNanoTime();
197 // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
198 // vsync is more than half a frame away.
199 if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
200 if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
201 mHandler.post(mSyntheticVSyncRunnable);
205 private long estimateLastVSyncTime(long currentTime) {
206 final long lastRefreshTime = mGoodStartingPointNano +
207 ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
208 return lastRefreshTime;
211 private void postRunnableCallback() {
212 assert !isVSyncSignalAvailable();
213 final long currentTime = getCurrentNanoTime();
214 final long lastRefreshTime = estimateLastVSyncTime(currentTime);
215 long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
216 assert delay > 0 && delay <= mRefreshPeriodNano;
218 if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
219 delay += mRefreshPeriodNano;
222 mLastPostedNano = currentTime + delay;
223 if (delay == 0) mHandler.post(mVSyncRunnableCallback);
224 else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);