root/content/public/android/java/src/org/chromium/content/browser/VSyncMonitor.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. onVSync
  2. getVSyncPeriodInMicroseconds
  3. isVSyncSignalAvailable
  4. stop
  5. unregisterListener
  6. requestUpdate
  7. setVSyncPointForICS
  8. getCurrentNanoTime
  9. onVSyncCallback
  10. postCallback
  11. postSyntheticVSync
  12. estimateLastVSyncTime
  13. postRunnableCallback

// Copyright 2012 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.content.browser;

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.view.Choreographer;
import android.view.WindowManager;

import org.chromium.base.TraceEvent;

/**
 * Notifies clients of the default displays's vertical sync pulses.
 * This class works in "burst" mode: once the update is requested, the listener will be
 * called MAX_VSYNC_COUNT times on the vertical sync pulses (on JB) or on every refresh
 * period (on ICS, see below), unless stop() is called.
 * On ICS, VSyncMonitor relies on setVSyncPointForICS() being called to set a reasonable
 * approximation of a vertical sync starting point; see also http://crbug.com/156397.
 */
public class VSyncMonitor {
    private static final long NANOSECONDS_PER_SECOND = 1000000000;
    private static final long NANOSECONDS_PER_MILLISECOND = 1000000;
    private static final long NANOSECONDS_PER_MICROSECOND = 1000;
    public static final int MAX_AUTO_ONVSYNC_COUNT = 5;

    /**
     * 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 final long mRefreshPeriodNano;

    private boolean mHaveRequestInFlight;
    private int mTriggerNextVSyncCount;

    // Choreographer is used to detect vsync on >= JB.
    private final Choreographer mChoreographer;
    private final Choreographer.FrameCallback mVSyncFrameCallback;

    // On ICS we just post a task through the handler (http://crbug.com/156397)
    private final Runnable mVSyncRunnableCallback;
    private long mGoodStartingPointNano;
    private long mLastPostedNano;

    // If the monitor is activated after having been idle, we synthesize the first vsync to reduce
    // latency.
    private final Handler mHandler = new Handler();
    private final Runnable mSyntheticVSyncRunnable;
    private long mLastVSyncCpuTimeNano;

    public VSyncMonitor(Context context, VSyncMonitor.Listener listener) {
        this(context, listener, true);
    }

    VSyncMonitor(Context context, VSyncMonitor.Listener listener, boolean enableJBVSync) {
        mListener = listener;
        float refreshRate = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay().getRefreshRate();
        if (refreshRate <= 0) refreshRate = 60;
        mRefreshPeriodNano = (long) (NANOSECONDS_PER_SECOND / refreshRate);
        mTriggerNextVSyncCount = 0;

        if (enableJBVSync && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // Use Choreographer on JB+ to get notified of vsync.
            mChoreographer = Choreographer.getInstance();
            mVSyncFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    TraceEvent.begin("VSync");
                    mGoodStartingPointNano = frameTimeNanos;
                    onVSyncCallback(frameTimeNanos, getCurrentNanoTime());
                    TraceEvent.end("VSync");
                }
            };
            mVSyncRunnableCallback = null;
        } else {
            // On ICS we just hope that running tasks is relatively predictable.
            mChoreographer = null;
            mVSyncFrameCallback = null;
            mVSyncRunnableCallback = new Runnable() {
                @Override
                public void run() {
                    TraceEvent.begin("VSyncTimer");
                    final long currentTime = getCurrentNanoTime();
                    onVSyncCallback(currentTime, currentTime);
                    TraceEvent.end("VSyncTimer");
                }
            };
            mLastPostedNano = 0;
        }
        mSyntheticVSyncRunnable = new Runnable() {
            @Override
            public void run() {
                TraceEvent.begin("VSyncSynthetic");
                final long currentTime = getCurrentNanoTime();
                onVSyncCallback(estimateLastVSyncTime(currentTime), currentTime);
                TraceEvent.end("VSyncSynthetic");
            }
        };
        mGoodStartingPointNano = getCurrentNanoTime();
    }

    /**
     * Returns the time interval between two consecutive vsync pulses in microseconds.
     */
    public long getVSyncPeriodInMicroseconds() {
        return mRefreshPeriodNano / NANOSECONDS_PER_MICROSECOND;
    }

    /**
     * Determine whether a true vsync signal is available on this platform.
     */
    public boolean isVSyncSignalAvailable() {
        return mChoreographer != null;
    }

    /**
     * Stop reporting vsync events. Note that at most one pending vsync event can still be delivered
     * after this function is called.
     */
    public void stop() {
        mTriggerNextVSyncCount = 0;
    }

    /**
     * Unregister the listener.
     * No vsync events will be reported afterwards.
     */
    public void unregisterListener() {
        stop();
        mListener = null;
    }

    /**
     * Request to be notified of the closest display vsync events.
     * Listener.onVSync() will be called soon after the upcoming vsync pulses.
     * It will be called at most MAX_AUTO_ONVSYNC_COUNT times unless requestUpdate() is called.
     */
    public void requestUpdate() {
        mTriggerNextVSyncCount = MAX_AUTO_ONVSYNC_COUNT;
        postCallback();
    }

    /**
     * Set the best guess of the point in the past when the vsync has happened.
     * @param goodStartingPointNano Known vsync point in the past.
     */
    public void setVSyncPointForICS(long goodStartingPointNano) {
        mGoodStartingPointNano = goodStartingPointNano;
    }

    private long getCurrentNanoTime() {
        return System.nanoTime();
    }

    private void onVSyncCallback(long frameTimeNanos, long currentTimeNanos) {
        assert mHaveRequestInFlight;
        mHaveRequestInFlight = false;
        mLastVSyncCpuTimeNano = currentTimeNanos;
        if (mTriggerNextVSyncCount >= 0) {
            mTriggerNextVSyncCount--;
            postCallback();
        }
        if (mListener != null) {
            mListener.onVSync(this, frameTimeNanos / NANOSECONDS_PER_MICROSECOND);
        }
    }

    private void postCallback() {
        if (mHaveRequestInFlight) return;
        mHaveRequestInFlight = true;
        if (postSyntheticVSync()) return;
        if (isVSyncSignalAvailable()) {
            mChoreographer.postFrameCallback(mVSyncFrameCallback);
        } else {
            postRunnableCallback();
        }
    }

    private boolean postSyntheticVSync() {
        final long currentTime = getCurrentNanoTime();
        // Only trigger a synthetic vsync if we've been idle for long enough and the upcoming real
        // vsync is more than half a frame away.
        if (currentTime - mLastVSyncCpuTimeNano < 2 * mRefreshPeriodNano) return false;
        if (currentTime - estimateLastVSyncTime(currentTime) > mRefreshPeriodNano / 2) return false;
        mHandler.post(mSyntheticVSyncRunnable);
        return true;
    }

    private long estimateLastVSyncTime(long currentTime) {
        final long lastRefreshTime = mGoodStartingPointNano +
                ((currentTime - mGoodStartingPointNano) / mRefreshPeriodNano) * mRefreshPeriodNano;
        return lastRefreshTime;
    }

    private void postRunnableCallback() {
        assert !isVSyncSignalAvailable();
        final long currentTime = getCurrentNanoTime();
        final long lastRefreshTime = estimateLastVSyncTime(currentTime);
        long delay = (lastRefreshTime + mRefreshPeriodNano) - currentTime;
        assert delay > 0 && delay <= mRefreshPeriodNano;

        if (currentTime + delay <= mLastPostedNano + mRefreshPeriodNano / 2) {
            delay += mRefreshPeriodNano;
        }

        mLastPostedNano = currentTime + delay;
        if (delay == 0) mHandler.post(mVSyncRunnableCallback);
        else mHandler.postDelayed(mVSyncRunnableCallback, delay / NANOSECONDS_PER_MILLISECOND);
    }
}

/* [<][>][^][v][top][bottom][index][help] */