michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import android.os.SystemClock; michael@0: michael@0: /** michael@0: * A custom-built data structure to assist with measuring draw times. michael@0: * michael@0: * This class maintains a fixed-size circular buffer of DisplayPortMetrics michael@0: * objects and associated timestamps. It provides only three operations, which michael@0: * is all we require for our purposes of measuring draw times. Note michael@0: * in particular that the class is designed so that even though it is michael@0: * accessed from multiple threads, it does not require synchronization; michael@0: * any concurrency errors that result from this are handled gracefully. michael@0: * michael@0: * Assuming an unrolled buffer so that mTail is greater than mHead, the data michael@0: * stored in the buffer at entries [mHead, mTail) will never be modified, and michael@0: * so are "safe" to read. If this reading is done on the same thread that michael@0: * owns mHead, then reading the range [mHead, mTail) is guaranteed to be safe michael@0: * since the range itself will not shrink. michael@0: */ michael@0: final class DrawTimingQueue { michael@0: private static final String LOGTAG = "GeckoDrawTimingQueue"; michael@0: private static final int BUFFER_SIZE = 16; michael@0: michael@0: private final DisplayPortMetrics[] mMetrics; michael@0: private final long[] mTimestamps; michael@0: michael@0: private int mHead; michael@0: private int mTail; michael@0: michael@0: DrawTimingQueue() { michael@0: mMetrics = new DisplayPortMetrics[BUFFER_SIZE]; michael@0: mTimestamps = new long[BUFFER_SIZE]; michael@0: mHead = BUFFER_SIZE - 1; michael@0: mTail = 0; michael@0: } michael@0: michael@0: /** michael@0: * Add a new entry to the tail of the queue. If the buffer is full, michael@0: * do nothing. This must only be called from the Java UI thread. michael@0: */ michael@0: boolean add(DisplayPortMetrics metrics) { michael@0: if (mHead == mTail) { michael@0: return false; michael@0: } michael@0: mMetrics[mTail] = metrics; michael@0: mTimestamps[mTail] = SystemClock.uptimeMillis(); michael@0: mTail = (mTail + 1) % BUFFER_SIZE; michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Find the timestamp associated with the given metrics, AND remove michael@0: * all metrics objects from the start of the queue up to and including michael@0: * the one provided. Note that because of draw coalescing, the metrics michael@0: * object passed in here may not be the one at the head of the queue, michael@0: * and so we must iterate our way through the list to find it. michael@0: * This must only be called from the compositor thread. michael@0: */ michael@0: long findTimeFor(DisplayPortMetrics metrics) { michael@0: // keep a copy of the tail pointer so that we ignore new items michael@0: // added to the queue while we are searching. this is fine because michael@0: // the one we are looking for will either have been added already michael@0: // or will not be in the queue at all. michael@0: int tail = mTail; michael@0: // walk through the "safe" range from mHead to tail; these entries michael@0: // will not be modified unless we change mHead. michael@0: int i = (mHead + 1) % BUFFER_SIZE; michael@0: while (i != tail) { michael@0: if (mMetrics[i].fuzzyEquals(metrics)) { michael@0: // found it, copy out the timestamp to a local var BEFORE michael@0: // changing mHead or add could clobber the timestamp. michael@0: long timestamp = mTimestamps[i]; michael@0: mHead = i; michael@0: return timestamp; michael@0: } michael@0: i = (i + 1) % BUFFER_SIZE; michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: /** michael@0: * Reset the buffer to empty. michael@0: * This must only be called from the compositor thread. michael@0: */ michael@0: void reset() { michael@0: // we can only modify mHead on this thread. michael@0: mHead = (mTail + BUFFER_SIZE - 1) % BUFFER_SIZE; michael@0: } michael@0: }