| 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 | } |