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