blob: 62687aa1b590c4d27b8fbe479791d7893a2d49ab [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;
import org.chromium.base.TraceEvent;
/**
* Notifies clients of the default displays's vertical sync pulses.
*/
public class VSyncMonitor {
private static final long NANOSECONDS_PER_SECOND = 1000000000;
private static final long NANOSECONDS_PER_MICROSECOND = 1000;
private boolean mInsideVSync;
// Conservative guess about vsync's consecutivity.
// If true, next tick is guaranteed to be consecutive.
private boolean mConsecutiveVSync;
/**
* VSync listener class
*/
public interface Listener {
/**
* Called very soon after the start of the display's vertical sync period.
* @param monitor The VSyncMonitor that triggered the signal.
* @param vsyncTimeMicros Absolute frame time in microseconds.
*/
public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros);
}
private Listener mListener;
// Display refresh rate as reported by the system.
private long mRefreshPeriodNano;
private boolean mUseEstimatedRefreshRate;
private boolean mHaveRequestInFlight;
private final Choreographer mChoreographer;
private final Choreographer.FrameCallback mVSyncFrameCallback;
private long mGoodStartingPointNano;
// If the monitor is activated after having been idle, we synthesize the first vsync to reduce
// latency.
private final Handler mHandler = new Handler();
/**
* Constructs a VSyncMonitor
* @param context The application context.
* @param listener The listener receiving VSync notifications.
*/
public VSyncMonitor(Context context, VSyncMonitor.Listener listener, float refreshRate) {
mListener = listener;
updateRefreshRate(refreshRate);
mChoreographer = Choreographer.getInstance();
mVSyncFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
TraceEvent.begin("VSync");
if (mUseEstimatedRefreshRate && mConsecutiveVSync) {
// Display.getRefreshRate() is unreliable on some platforms.
// Adjust refresh period- initial value is based on Display.getRefreshRate()
// after that it asymptotically approaches the real value.
long lastRefreshDurationNano = frameTimeNanos - mGoodStartingPointNano;
float lastRefreshDurationWeight = 0.1f;
mRefreshPeriodNano += (long) (lastRefreshDurationWeight
* (lastRefreshDurationNano - mRefreshPeriodNano));
}
mGoodStartingPointNano = frameTimeNanos;
onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
TraceEvent.end("VSync");
}
};
mGoodStartingPointNano = getCurrentNanoTime();
}
public void updateRefreshRate(float refreshRate) {
mUseEstimatedRefreshRate = refreshRate < 30;
if (refreshRate <= 0) refreshRate = 60;
mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
}
/**
* Returns the time interval between two consecutive vsync pulses in microseconds.
*/
public long getVSyncPeriodInMicroseconds() {
return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
}
/**
* Request to be notified of the closest display vsync events. This should
* always be called on the same thread used to create the VSyncMonitor.
* Listener.onVSync() will be called soon after the upcoming vsync pulses.
*/
public void requestUpdate() {
assert mHandler.getLooper() == Looper.myLooper();
postCallback();
}
/**
* @return true if onVSync handler is executing. If onVSync handler
* introduces invalidations, View#invalidate() should be called. If
* View#postInvalidateOnAnimation is called instead, the corresponding onDraw
* will be delayed by one frame. The embedder of VSyncMonitor should check
* this value if it wants to post an invalidation.
*/
public boolean isInsideVSync() {
return mInsideVSync;
}
private long getCurrentNanoTime() {
return System.nanoTime();
}
private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
assert mHaveRequestInFlight;
mInsideVSync = true;
mHaveRequestInFlight = false;
try {
if (mListener != null) {
mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
}
} finally {
mInsideVSync = false;
}
}
private void postCallback() {
if (mHaveRequestInFlight) return;
mHaveRequestInFlight = true;
mConsecutiveVSync = mInsideVSync;
mChoreographer.postFrameCallback(mVSyncFrameCallback);
}
}