Pay attention to buffer timestamps
When acquiring a buffer, SurfaceFlinger now computes the expected presentation time and passes it to the BufferQueue acquireBuffer() method. If it's not yet time to display the buffer, acquireBuffer() returns PRESENT_LATER instead of a buffer. The current implementation of the expected-present-time computation uses approximations and guesswork. Bug 7900302 Change-Id: If9345611c5983a11a811935aaf27d6388a5036f1
This commit is contained in:
parent
06b6aed2f1
commit
1585c4d9fb
|
@ -71,7 +71,8 @@ class BufferItemConsumer: public ConsumerBase
|
|||
//
|
||||
// If waitForFence is true, and the acquired BufferItem has a valid fence object,
|
||||
// acquireBuffer will wait on the fence with no timeout before returning.
|
||||
status_t acquireBuffer(BufferItem *item, bool waitForFence = true);
|
||||
status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen,
|
||||
bool waitForFence = true);
|
||||
|
||||
// Returns an acquired buffer to the queue, allowing it to be reused. Since
|
||||
// only a fixed number of buffers may be acquired at a time, old buffers
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
enum { NUM_BUFFER_SLOTS = 32 };
|
||||
enum { NO_CONNECTED_API = 0 };
|
||||
enum { INVALID_BUFFER_SLOT = -1 };
|
||||
enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE };
|
||||
enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE, PRESENT_LATER };
|
||||
|
||||
// When in async mode we reserve two slots in order to guarantee that the
|
||||
// producer and consumer can run asynchronously.
|
||||
|
@ -284,7 +284,13 @@ public:
|
|||
// acquired then the BufferItem::mGraphicBuffer field of buffer is set to
|
||||
// NULL and it is assumed that the consumer still holds a reference to the
|
||||
// buffer.
|
||||
status_t acquireBuffer(BufferItem *buffer);
|
||||
//
|
||||
// If presentWhen is nonzero, it indicates the time when the buffer will
|
||||
// be displayed on screen. If the buffer's timestamp is farther in the
|
||||
// future, the buffer won't be acquired, and PRESENT_LATER will be
|
||||
// returned. The presentation time is in nanoseconds, and the time base
|
||||
// is CLOCK_MONOTONIC.
|
||||
status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen);
|
||||
|
||||
// releaseBuffer releases a buffer slot from the consumer back to the
|
||||
// BufferQueue. This may be done while the buffer's contents are still
|
||||
|
|
|
@ -152,7 +152,8 @@ protected:
|
|||
// initialization that must take place the first time a buffer is assigned
|
||||
// to a slot. If it is overridden the derived class's implementation must
|
||||
// call ConsumerBase::acquireBufferLocked.
|
||||
virtual status_t acquireBufferLocked(BufferQueue::BufferItem *item);
|
||||
virtual status_t acquireBufferLocked(BufferQueue::BufferItem *item,
|
||||
nsecs_t presentWhen);
|
||||
|
||||
// releaseBufferLocked relinquishes control over a buffer, returning that
|
||||
// control to the BufferQueue.
|
||||
|
|
|
@ -237,7 +237,8 @@ protected:
|
|||
|
||||
// acquireBufferLocked overrides the ConsumerBase method to update the
|
||||
// mEglSlots array in addition to the ConsumerBase behavior.
|
||||
virtual status_t acquireBufferLocked(BufferQueue::BufferItem *item);
|
||||
virtual status_t acquireBufferLocked(BufferQueue::BufferItem *item,
|
||||
nsecs_t presentWhen);
|
||||
|
||||
// releaseBufferLocked overrides the ConsumerBase method to update the
|
||||
// mEglSlots array in addition to the ConsumerBase.
|
||||
|
|
|
@ -47,14 +47,15 @@ void BufferItemConsumer::setName(const String8& name) {
|
|||
mBufferQueue->setConsumerName(name);
|
||||
}
|
||||
|
||||
status_t BufferItemConsumer::acquireBuffer(BufferItem *item, bool waitForFence) {
|
||||
status_t BufferItemConsumer::acquireBuffer(BufferItem *item,
|
||||
nsecs_t presentWhen, bool waitForFence) {
|
||||
status_t err;
|
||||
|
||||
if (!item) return BAD_VALUE;
|
||||
|
||||
Mutex::Autolock _l(mMutex);
|
||||
|
||||
err = acquireBufferLocked(item);
|
||||
err = acquireBufferLocked(item, presentWhen);
|
||||
if (err != OK) {
|
||||
if (err != NO_BUFFER_AVAILABLE) {
|
||||
BI_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
|
||||
|
|
|
@ -807,7 +807,7 @@ void BufferQueue::freeAllBuffersLocked() {
|
|||
}
|
||||
}
|
||||
|
||||
status_t BufferQueue::acquireBuffer(BufferItem *buffer) {
|
||||
status_t BufferQueue::acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) {
|
||||
ATRACE_CALL();
|
||||
Mutex::Autolock _l(mMutex);
|
||||
|
||||
|
@ -830,38 +830,68 @@ status_t BufferQueue::acquireBuffer(BufferItem *buffer) {
|
|||
// check if queue is empty
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
if (!mQueue.empty()) {
|
||||
Fifo::iterator front(mQueue.begin());
|
||||
int buf = front->mBuf;
|
||||
*buffer = *front;
|
||||
ATRACE_BUFFER_INDEX(buf);
|
||||
|
||||
ST_LOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
|
||||
front->mBuf, front->mFrameNumber,
|
||||
front->mGraphicBuffer->handle);
|
||||
// if front buffer still being tracked update slot state
|
||||
if (stillTracking(front)) {
|
||||
mSlots[buf].mAcquireCalled = true;
|
||||
mSlots[buf].mNeedsCleanupOnRelease = false;
|
||||
mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
|
||||
mSlots[buf].mFence = Fence::NO_FENCE;
|
||||
}
|
||||
|
||||
// If the buffer has previously been acquired by the consumer, set
|
||||
// mGraphicBuffer to NULL to avoid unnecessarily remapping this
|
||||
// buffer on the consumer side.
|
||||
if (buffer->mAcquireCalled) {
|
||||
buffer->mGraphicBuffer = NULL;
|
||||
}
|
||||
|
||||
mQueue.erase(front);
|
||||
mDequeueCondition.broadcast();
|
||||
|
||||
ATRACE_INT(mConsumerName.string(), mQueue.size());
|
||||
} else {
|
||||
if (mQueue.empty()) {
|
||||
return NO_BUFFER_AVAILABLE;
|
||||
}
|
||||
|
||||
Fifo::iterator front(mQueue.begin());
|
||||
int buf = front->mBuf;
|
||||
|
||||
// Compare the buffer's desired presentation time to the predicted
|
||||
// actual display time.
|
||||
//
|
||||
// The "presentWhen" argument indicates when the buffer is expected
|
||||
// to be presented on-screen. If the buffer's desired-present time
|
||||
// is earlier (less) than presentWhen, meaning it'll be displayed
|
||||
// on time or possibly late, we acquire and return it. If we don't want
|
||||
// to display it until after the presentWhen time, we return PRESENT_LATER
|
||||
// without acquiring it.
|
||||
//
|
||||
// To be safe, we don't refuse to acquire the buffer if presentWhen is
|
||||
// more than one second in the future beyond the desired present time
|
||||
// (i.e. we'd be holding the buffer for a really long time).
|
||||
const int MAX_FUTURE_NSEC = 1000000000ULL;
|
||||
nsecs_t desiredPresent = front->mTimestamp;
|
||||
if (presentWhen != 0 && desiredPresent > presentWhen &&
|
||||
desiredPresent - presentWhen < MAX_FUTURE_NSEC)
|
||||
{
|
||||
ALOGV("pts defer: des=%lld when=%lld (%lld) now=%lld",
|
||||
desiredPresent, presentWhen, desiredPresent - presentWhen,
|
||||
systemTime(CLOCK_MONOTONIC));
|
||||
return PRESENT_LATER;
|
||||
}
|
||||
if (presentWhen != 0) {
|
||||
ALOGV("pts accept: %p[%d] sig=%lld des=%lld when=%lld (%lld)",
|
||||
mSlots, buf, mSlots[buf].mFence->getSignalTime(),
|
||||
desiredPresent, presentWhen, desiredPresent - presentWhen);
|
||||
}
|
||||
|
||||
*buffer = *front;
|
||||
ATRACE_BUFFER_INDEX(buf);
|
||||
|
||||
ST_LOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
|
||||
front->mBuf, front->mFrameNumber,
|
||||
front->mGraphicBuffer->handle);
|
||||
// if front buffer still being tracked update slot state
|
||||
if (stillTracking(front)) {
|
||||
mSlots[buf].mAcquireCalled = true;
|
||||
mSlots[buf].mNeedsCleanupOnRelease = false;
|
||||
mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
|
||||
mSlots[buf].mFence = Fence::NO_FENCE;
|
||||
}
|
||||
|
||||
// If the buffer has previously been acquired by the consumer, set
|
||||
// mGraphicBuffer to NULL to avoid unnecessarily remapping this
|
||||
// buffer on the consumer side.
|
||||
if (buffer->mAcquireCalled) {
|
||||
buffer->mGraphicBuffer = NULL;
|
||||
}
|
||||
|
||||
mQueue.erase(front);
|
||||
mDequeueCondition.broadcast();
|
||||
|
||||
ATRACE_INT(mConsumerName.string(), mQueue.size());
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
|
|
|
@ -182,8 +182,9 @@ void ConsumerBase::dumpLocked(String8& result, const char* prefix) const {
|
|||
}
|
||||
}
|
||||
|
||||
status_t ConsumerBase::acquireBufferLocked(BufferQueue::BufferItem *item) {
|
||||
status_t err = mBufferQueue->acquireBuffer(item);
|
||||
status_t ConsumerBase::acquireBufferLocked(BufferQueue::BufferItem *item,
|
||||
nsecs_t presentWhen) {
|
||||
status_t err = mBufferQueue->acquireBuffer(item, presentWhen);
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ status_t CpuConsumer::lockNextBuffer(LockedBuffer *nativeBuffer) {
|
|||
|
||||
Mutex::Autolock _l(mMutex);
|
||||
|
||||
err = acquireBufferLocked(&b);
|
||||
err = acquireBufferLocked(&b, 0);
|
||||
if (err != OK) {
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
return BAD_VALUE;
|
||||
|
|
|
@ -139,7 +139,7 @@ status_t GLConsumer::updateTexImage() {
|
|||
// Acquire the next buffer.
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
err = acquireBufferLocked(&item);
|
||||
err = acquireBufferLocked(&item, 0);
|
||||
if (err != NO_ERROR) {
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
// We always bind the texture even if we don't update its contents.
|
||||
|
@ -165,8 +165,9 @@ status_t GLConsumer::updateTexImage() {
|
|||
return bindTextureImageLocked();
|
||||
}
|
||||
|
||||
status_t GLConsumer::acquireBufferLocked(BufferQueue::BufferItem *item) {
|
||||
status_t err = ConsumerBase::acquireBufferLocked(item);
|
||||
status_t GLConsumer::acquireBufferLocked(BufferQueue::BufferItem *item,
|
||||
nsecs_t presentWhen) {
|
||||
status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen);
|
||||
if (err != NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) {
|
|||
GRALLOC_USAGE_SW_READ_OFTEN));
|
||||
ASSERT_EQ(OK, mBQ->requestBuffer(slot, &buf));
|
||||
ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo));
|
||||
ASSERT_EQ(OK, mBQ->acquireBuffer(&item));
|
||||
ASSERT_EQ(OK, mBQ->acquireBuffer(&item, 0));
|
||||
}
|
||||
|
||||
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
|
||||
|
@ -90,7 +90,7 @@ TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) {
|
|||
ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo));
|
||||
|
||||
// Acquire the third buffer, which should fail.
|
||||
ASSERT_EQ(INVALID_OPERATION, mBQ->acquireBuffer(&item));
|
||||
ASSERT_EQ(INVALID_OPERATION, mBQ->acquireBuffer(&item, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferQueueTest, SetMaxAcquiredBufferCountWithIllegalValues_ReturnsError) {
|
||||
|
|
|
@ -83,7 +83,7 @@ status_t FramebufferSurface::nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>&
|
|||
Mutex::Autolock lock(mMutex);
|
||||
|
||||
BufferQueue::BufferItem item;
|
||||
status_t err = acquireBufferLocked(&item);
|
||||
status_t err = acquireBufferLocked(&item, 0);
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
outBuffer = mCurrentBuffer;
|
||||
return NO_ERROR;
|
||||
|
|
|
@ -1051,11 +1051,6 @@ Region Layer::latchBuffer(bool& recomputeVisibleRegions)
|
|||
const bool oldOpacity = isOpaque();
|
||||
sp<GraphicBuffer> oldActiveBuffer = mActiveBuffer;
|
||||
|
||||
// signal another event if we have more frames pending
|
||||
if (android_atomic_dec(&mQueuedFrames) > 1) {
|
||||
mFlinger->signalLayerUpdate();
|
||||
}
|
||||
|
||||
struct Reject : public SurfaceFlingerConsumer::BufferRejecter {
|
||||
Layer::State& front;
|
||||
Layer::State& current;
|
||||
|
@ -1161,7 +1156,21 @@ Region Layer::latchBuffer(bool& recomputeVisibleRegions)
|
|||
|
||||
Reject r(mDrawingState, getCurrentState(), recomputeVisibleRegions);
|
||||
|
||||
if (mSurfaceFlingerConsumer->updateTexImage(&r) != NO_ERROR) {
|
||||
status_t updateResult = mSurfaceFlingerConsumer->updateTexImage(&r);
|
||||
if (updateResult == BufferQueue::PRESENT_LATER) {
|
||||
// Producer doesn't want buffer to be displayed yet. Signal a
|
||||
// layer update so we check again at the next opportunity.
|
||||
mFlinger->signalLayerUpdate();
|
||||
return outDirtyRegion;
|
||||
}
|
||||
|
||||
// Decrement the queued-frames count. Signal another event if we
|
||||
// have more frames pending.
|
||||
if (android_atomic_dec(&mQueuedFrames) > 1) {
|
||||
mFlinger->signalLayerUpdate();
|
||||
}
|
||||
|
||||
if (updateResult != NO_ERROR) {
|
||||
// something happened!
|
||||
recomputeVisibleRegions = true;
|
||||
return outDirtyRegion;
|
||||
|
|
|
@ -50,12 +50,14 @@ status_t SurfaceFlingerConsumer::updateTexImage(BufferRejecter* rejecter)
|
|||
// Acquire the next buffer.
|
||||
// In asynchronous mode the list is guaranteed to be one buffer
|
||||
// deep, while in synchronous mode we use the oldest buffer.
|
||||
err = acquireBufferLocked(&item);
|
||||
err = acquireBufferLocked(&item, computeExpectedPresent());
|
||||
if (err != NO_ERROR) {
|
||||
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
|
||||
// This variant of updateTexImage does not guarantee that the
|
||||
// texture is bound, so no need to call glBindTexture.
|
||||
err = NO_ERROR;
|
||||
} else if (err == BufferQueue::PRESENT_LATER) {
|
||||
// return the error, without logging
|
||||
} else {
|
||||
ALOGE("updateTexImage: acquire failed: %s (%d)",
|
||||
strerror(-err), err);
|
||||
|
@ -99,6 +101,48 @@ status_t SurfaceFlingerConsumer::bindTextureImage()
|
|||
return bindTextureImageLocked();
|
||||
}
|
||||
|
||||
// We need to determine the time when a buffer acquired now will be
|
||||
// displayed. This can be calculated:
|
||||
// time when previous buffer's actual-present fence was signaled
|
||||
// + current display refresh rate * HWC latency
|
||||
// + a little extra padding
|
||||
//
|
||||
// Buffer producers are expected to set their desired presentation time
|
||||
// based on choreographer time stamps, which (coming from vsync events)
|
||||
// will be slightly later then the actual-present timing. If we get a
|
||||
// desired-present time that is unintentionally a hair after the next
|
||||
// vsync, we'll hold the frame when we really want to display it. We
|
||||
// want to use an expected-presentation time that is slightly late to
|
||||
// avoid this sort of edge case.
|
||||
nsecs_t SurfaceFlingerConsumer::computeExpectedPresent()
|
||||
{
|
||||
// Don't yet have an easy way to get actual buffer flip time for
|
||||
// the specific display, so use the current time. This is typically
|
||||
// 1.3ms past the vsync event time.
|
||||
const nsecs_t prevVsync = systemTime(CLOCK_MONOTONIC);
|
||||
|
||||
// Given a SurfaceFlinger reference, and information about what display
|
||||
// we're destined for, we could query the HWC for the refresh rate. This
|
||||
// could change over time, e.g. we could switch to 24fps for a movie.
|
||||
// For now, assume 60fps.
|
||||
//const nsecs_t vsyncPeriod =
|
||||
// getHwComposer().getRefreshPeriod(HWC_DISPLAY_PRIMARY);
|
||||
const nsecs_t vsyncPeriod = 16700000;
|
||||
|
||||
// The HWC doesn't currently have a way to report additional latency.
|
||||
// Assume that whatever we submit now will appear on the next flip,
|
||||
// i.e. 1 frame of latency w.r.t. the previous flip.
|
||||
const uint32_t hwcLatency = 1;
|
||||
|
||||
// A little extra padding to compensate for slack between actual vsync
|
||||
// time and vsync event receipt. Currently not needed since we're
|
||||
// using "now" instead of a vsync time.
|
||||
const nsecs_t extraPadding = 0;
|
||||
|
||||
// Total it up.
|
||||
return prevVsync + hwcLatency * vsyncPeriod + extraPadding;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
}; // namespace android
|
||||
|
||||
|
|
|
@ -51,6 +51,9 @@ public:
|
|||
|
||||
// See GLConsumer::bindTextureImageLocked().
|
||||
status_t bindTextureImage();
|
||||
|
||||
private:
|
||||
nsecs_t computeExpectedPresent();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue