Add BufferQueueInterposer and use it for virtual displays

BufferQueueInterposer allows a client to tap into a
IGraphicBufferProducer-based buffer queue, and modify buffers as they
pass from producer to consumer. VirtualDisplaySurface uses this to
layer HWC composition on top of GLES composition before passing the
buffer to the virtual display consumer.

Bug: 8384764
Change-Id: I61ae54f3d90de6a35f4f02bb5e64e7cc88e1cb83
diff --git a/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.cpp b/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.cpp
new file mode 100644
index 0000000..d8ad224
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "BQInterposer"
+
+#include "BufferQueueInterposer.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+#define BQI_LOGV(x, ...) ALOGV("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define BQI_LOGD(x, ...) ALOGD("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define BQI_LOGI(x, ...) ALOGI("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define BQI_LOGW(x, ...) ALOGW("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define BQI_LOGE(x, ...) ALOGE("[%s] "x, mName.string(), ##__VA_ARGS__)
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+    static volatile int32_t globalCounter = 0;
+    return android_atomic_inc(&globalCounter);
+}
+
+BufferQueueInterposer::BufferQueueInterposer(
+        const sp<IGraphicBufferProducer>& sink, const String8& name)
+:   mSink(sink),
+    mName(name),
+    mAcquired(false)
+{
+    BQI_LOGV("BufferQueueInterposer sink=%p", sink.get());
+
+    // We need one additional dequeued buffer beyond what the source needs.
+    // To have more than one (the default), we must call setBufferCount. But
+    // we have no way of knowing what the sink has set as the minimum buffer
+    // count, so if we just call setBufferCount(3) it may fail (and does, on
+    // one device using a video encoder sink). So far on the devices we care
+    // about, this is the smallest value that works.
+    //
+    // TODO: Change IGraphicBufferProducer and implementations to support this.
+    // Maybe change it so both the consumer and producer declare how many
+    // buffers they need, and the IGBP adds them? Then BQInterposer would just
+    // add 1 to the source's buffer count.
+    mSink->setBufferCount(6);
+}
+
+BufferQueueInterposer::~BufferQueueInterposer() {
+    Mutex::Autolock lock(mMutex);
+    flushQueuedBuffersLocked();
+    BQI_LOGV("~BufferQueueInterposer");
+}
+
+status_t BufferQueueInterposer::requestBuffer(int slot,
+        sp<GraphicBuffer>* outBuf) {
+    BQI_LOGV("requestBuffer slot=%d", slot);
+    Mutex::Autolock lock(mMutex);
+
+    if (size_t(slot) >= mBuffers.size()) {
+        size_t size = mBuffers.size();
+        mBuffers.insertAt(size, size - slot + 1);
+    }
+    sp<GraphicBuffer>& buf = mBuffers.editItemAt(slot);
+
+    status_t result = mSink->requestBuffer(slot, &buf);
+    *outBuf = buf;
+    return result;
+}
+
+status_t BufferQueueInterposer::setBufferCount(int bufferCount) {
+    BQI_LOGV("setBufferCount count=%d", bufferCount);
+    Mutex::Autolock lock(mMutex);
+
+    bufferCount += 1;
+
+    status_t result = flushQueuedBuffersLocked();
+    if (result != NO_ERROR)
+        return result;
+
+    result = mSink->setBufferCount(bufferCount);
+    if (result != NO_ERROR)
+        return result;
+
+    for (size_t i = 0; i < mBuffers.size(); i++)
+        mBuffers.editItemAt(i).clear();
+    ssize_t n = mBuffers.resize(bufferCount);
+    result = (n < 0) ? n : result;
+
+    return result;
+}
+
+status_t BufferQueueInterposer::dequeueBuffer(int* slot, sp<Fence>* fence,
+            uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+    BQI_LOGV("dequeueBuffer %ux%u fmt=%u usage=%#x", w, h, format, usage);
+    return mSink->dequeueBuffer(slot, fence, w, h, format, usage);
+}
+
+status_t BufferQueueInterposer::queueBuffer(int slot,
+            const QueueBufferInput& input, QueueBufferOutput* output) {
+    BQI_LOGV("queueBuffer slot=%d", slot);
+    Mutex::Autolock lock(mMutex);
+    mQueue.push(QueuedBuffer(slot, input));
+    *output = mQueueBufferOutput;
+    return NO_ERROR;
+}
+
+void BufferQueueInterposer::cancelBuffer(int slot, const sp<Fence>& fence) {
+    BQI_LOGV("cancelBuffer slot=%d", slot);
+    mSink->cancelBuffer(slot, fence);
+}
+
+int BufferQueueInterposer::query(int what, int* value) {
+    BQI_LOGV("query what=%d", what);
+    return mSink->query(what, value);
+}
+
+status_t BufferQueueInterposer::setSynchronousMode(bool enabled) {
+    BQI_LOGV("setSynchronousMode %s", enabled ? "true" : "false");
+    return mSink->setSynchronousMode(enabled);
+}
+
+status_t BufferQueueInterposer::connect(int api, QueueBufferOutput* output) {
+    BQI_LOGV("connect api=%d", api);
+    Mutex::Autolock lock(mMutex);
+    status_t result = mSink->connect(api, &mQueueBufferOutput);
+    if (result == NO_ERROR) {
+        *output = mQueueBufferOutput;
+    }
+    return result;
+}
+
+status_t BufferQueueInterposer::disconnect(int api) {
+    BQI_LOGV("disconnect: api=%d", api);
+    Mutex::Autolock lock(mMutex);
+    flushQueuedBuffersLocked();
+    return mSink->disconnect(api);
+}
+
+status_t BufferQueueInterposer::pullEmptyBuffer() {
+    status_t result;
+
+    int slot;
+    sp<Fence> fence;
+    result = dequeueBuffer(&slot, &fence, 0, 0, 0, 0);
+    if (result == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+        sp<GraphicBuffer> buffer;
+        result = requestBuffer(slot, &buffer);
+    } else if (result != NO_ERROR) {
+        return result;
+    }
+
+    uint32_t w, h, transformHint, numPendingBuffers;
+    mQueueBufferOutput.deflate(&w, &h, &transformHint, &numPendingBuffers);
+
+    IGraphicBufferProducer::QueueBufferInput qbi(0, Rect(w, h),
+            NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, fence);
+    IGraphicBufferProducer::QueueBufferOutput qbo;
+    result = queueBuffer(slot, qbi, &qbo);
+    if (result != NO_ERROR)
+        return result;
+
+    return NO_ERROR;
+}
+
+status_t BufferQueueInterposer::acquireBuffer(sp<GraphicBuffer>* buf,
+        sp<Fence>* fence) {
+    Mutex::Autolock lock(mMutex);
+    if (mQueue.empty()) {
+        BQI_LOGV("acquireBuffer: no buffers available");
+        return NO_BUFFER_AVAILABLE;
+    }
+    if (mAcquired) {
+        BQI_LOGE("acquireBuffer: buffer already acquired");
+        return BUFFER_ALREADY_ACQUIRED;
+    }
+    BQI_LOGV("acquireBuffer: acquiring slot %d", mQueue[0].slot);
+
+    *buf = mBuffers[mQueue[0].slot];
+    *fence = mQueue[0].fence;
+    mAcquired = true;
+    return NO_ERROR;
+}
+
+status_t BufferQueueInterposer::releaseBuffer(const sp<Fence>& fence) {
+    Mutex::Autolock lock(mMutex);
+    if (!mAcquired) {
+        BQI_LOGE("releaseBuffer: releasing a non-acquired buffer");
+        return BUFFER_NOT_ACQUIRED;
+    }
+    BQI_LOGV("releaseBuffer: releasing slot %d to sink", mQueue[0].slot);
+
+    const QueuedBuffer& b = mQueue[0];
+    status_t result = mSink->queueBuffer(b.slot,
+            QueueBufferInput(b.timestamp, b.crop, b.scalingMode,
+                b.transform, b.fence),
+            &mQueueBufferOutput);
+    mQueue.removeAt(0);
+    mAcquired = false;
+
+    return result;
+}
+
+status_t BufferQueueInterposer::flushQueuedBuffersLocked() {
+    if (mAcquired) {
+        BQI_LOGE("flushQueuedBuffersLocked: buffer acquired, can't flush");
+        return INVALID_OPERATION;
+    }
+
+    status_t result = NO_ERROR;
+    for (size_t i = 0; i < mQueue.size(); i++) {
+        const QueuedBuffer& b = mQueue[i];
+        BQI_LOGV("flushing queued slot %d to sink", b.slot);
+        status_t err = mSink->queueBuffer(b.slot,
+                QueueBufferInput(b.timestamp, b.crop, b.scalingMode,
+                    b.transform, b.fence),
+                &mQueueBufferOutput);
+        if (err != NO_ERROR && result == NO_ERROR) // latch first error
+            result = err;
+    }
+    mQueue.clear();
+    return result;
+}
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.h b/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.h
new file mode 100644
index 0000000..7208630
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/BufferQueueInterposer.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_BUFFERQUEUEINTERPOSER_H
+#define ANDROID_SF_BUFFERQUEUEINTERPOSER_H
+
+#include <gui/IGraphicBufferProducer.h>
+#include <utils/Mutex.h>
+#include <utils/Vector.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+// BufferQueueInterposers introduce an extra stage between a buffer producer
+// (the source) and a buffer consumer (the sink), which communicate via the
+// IGraphicBufferProducer interface. It is designed to be as transparent as
+// possible to both endpoints, so that they can work the same whether an
+// interposer is present or not.
+//
+// When the interpose is present, the source queues buffers to the
+// IGraphicBufferProducer implemented by BufferQueueInterposer. A client of
+// the BufferQueueInterposer can acquire each buffer in turn and read or
+// modify it, releasing the buffer when finished. When the buffer is released,
+// the BufferQueueInterposer queues it to the original IGraphicBufferProducer
+// interface representing the sink.
+//
+// A BufferQueueInterposer can be used to do additional rendering to a buffer
+// before it is consumed -- essentially pipelining two producers. As an
+// example, SurfaceFlinger uses this to implement mixed GLES and HWC
+// compositing to the same buffer for virtual displays. If it used two separate
+// buffer queues, then in GLES-only or mixed GLES+HWC compositing, the HWC
+// would have to copy the GLES output buffer to the HWC output buffer, using
+// more bandwidth than having HWC do additional composition "in place" on the
+// GLES output buffer.
+//
+// The goal for this class is to be usable in a variety of situations and be
+// part of libgui. But both the interface and implementation need some
+// iteration before then, so for now it should only be used by
+// VirtualDisplaySurface, which is why it's currently in SurfaceFlinger.
+//
+// Some of the problems that still need to be solved are:
+//
+// - Refactor the interposer interface along with BufferQueue and ConsumerBase,
+//   so that there is a common interface for the consumer end of a queue. The
+//   existing interfaces have some problems when the implementation isn't the
+//   final consumer.
+//
+// - The interposer needs at least one buffer in addition to those used by the
+//   source and sink. setBufferCount and QueueBufferOutput both need to
+//   account for this. It's not possible currently to do this generically,
+//   since we can't find out how many buffers the source and sink need. (See
+//   the horrible hack in the BufferQueueInterposer constructor).
+//
+// - Abandoning, disconnecting, and connecting need to pass through somehow.
+//   There needs to be a way to tell the interposer client to release its
+//   buffer immediately so it can be queued/released, e.g. when the source
+//   calls disconnect().
+//
+// - Right now the source->BQI queue is synchronous even if the BQI->sink
+//   queue is asynchronous. Need to figure out how asynchronous should behave
+//   and implement that.
+
+class BufferQueueInterposer : public BnGraphicBufferProducer {
+public:
+    BufferQueueInterposer(const sp<IGraphicBufferProducer>& sink,
+            const String8& name);
+
+    //
+    // IGraphicBufferProducer interface
+    //
+    virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* outBuf);
+    virtual status_t setBufferCount(int bufferCount);
+    virtual status_t dequeueBuffer(int* slot, sp<Fence>* fence,
+            uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
+    virtual status_t queueBuffer(int slot,
+            const QueueBufferInput& input, QueueBufferOutput* output);
+    virtual void cancelBuffer(int slot, const sp<Fence>& fence);
+    virtual int query(int what, int* value);
+    virtual status_t setSynchronousMode(bool enabled);
+    virtual status_t connect(int api, QueueBufferOutput* output);
+    virtual status_t disconnect(int api);
+
+    //
+    // Interposer interface
+    //
+
+    enum {
+        NO_BUFFER_AVAILABLE = 2,    // matches BufferQueue
+        BUFFER_NOT_ACQUIRED,
+        BUFFER_ALREADY_ACQUIRED,
+    };
+
+    // Acquire the oldest queued buffer. If no buffers are pending, returns
+    // NO_BUFFER_AVAILABLE. If a buffer is currently acquired, returns
+    // BUFFER_ALREADY_ACQUIRED.
+    status_t acquireBuffer(sp<GraphicBuffer>* buf, sp<Fence>* fence);
+
+    // Release the currently acquired buffer, queueing it to the sink. If the
+    // current buffer hasn't been acquired, returns BUFFER_NOT_ACQUIRED.
+    status_t releaseBuffer(const sp<Fence>& fence);
+
+    // pullEmptyBuffer dequeues a buffer from the sink, then immediately
+    // queues it to the interposer. This makes a buffer available for the
+    // client to acquire even if the source hasn't queued one.
+    status_t pullEmptyBuffer();
+
+private:
+    struct QueuedBuffer {
+        QueuedBuffer(): slot(-1) {}
+        QueuedBuffer(int slot, const QueueBufferInput& qbi): slot(slot) {
+            qbi.deflate(&timestamp, &crop, &scalingMode, &transform, &fence);
+        }
+        int slot;
+        int64_t timestamp;
+        Rect crop;
+        int scalingMode;
+        uint32_t transform;
+        sp<Fence> fence;
+    };
+
+    virtual ~BufferQueueInterposer();
+    status_t flushQueuedBuffersLocked();
+
+    const sp<IGraphicBufferProducer> mSink;
+    String8 mName;
+
+    Mutex mMutex;
+    Vector<sp<GraphicBuffer> > mBuffers;
+    Vector<QueuedBuffer> mQueue;
+    bool mAcquired;
+    QueueBufferOutput mQueueBufferOutput;
+};
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_BUFFERQUEUEINTERPOSER_H
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 4b6c413..eeb1fef 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -655,6 +655,18 @@
             mLists[0]->sur = eglGetCurrentSurface(EGL_DRAW);
         }
 
+        // For virtual displays, the framebufferTarget buffer also serves as
+        // the HWC output buffer, so we need to copy the buffer handle and
+        // dup() the acquire fence.
+        for (size_t i=HWC_NUM_DISPLAY_TYPES; i<mNumDisplays; i++) {
+            DisplayData& disp(mDisplayData[i]);
+            if (disp.framebufferTarget) {
+                mLists[i]->outbuf = disp.framebufferTarget->handle;
+                mLists[i]->outbufAcquireFenceFd =
+                        dup(disp.framebufferTarget->acquireFenceFd);
+            }
+        }
+
         err = mHwc->set(mHwc, mNumDisplays, mLists);
 
         for (size_t i=0 ; i<mNumDisplays ; i++) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 9816a45..92e4cbf 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -112,7 +112,7 @@
     // does this display have layers handled by GLES
     bool hasGlesComposition(int32_t id) const;
 
-    // get the releaseFence file descriptor for the given display
+    // get the releaseFence file descriptor for a display's framebuffer layer.
     // the release fence is only valid after commit()
     int getAndResetReleaseFenceFd(int32_t id);
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index d30e9bf..433e1eb 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <gui/IGraphicBufferProducer.h>
 #include "VirtualDisplaySurface.h"
+#include "HWComposer.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
@@ -25,15 +25,22 @@
         const sp<IGraphicBufferProducer>& sink, const String8& name)
 :   mHwc(hwc),
     mDisplayId(disp),
-    mSink(sink),
-    mName(name)
+    mSource(new BufferQueueInterposer(sink, name)),
+    mName(name),
+    mReleaseFence(Fence::NO_FENCE)
 {}
 
 VirtualDisplaySurface::~VirtualDisplaySurface() {
+    if (mAcquiredBuffer != NULL) {
+        status_t result = mSource->releaseBuffer(mReleaseFence);
+        ALOGE_IF(result != NO_ERROR, "VirtualDisplaySurface \"%s\": "
+                "failed to release previous buffer: %d",
+                mName.string(), result);
+    }
 }
 
 sp<IGraphicBufferProducer> VirtualDisplaySurface::getIGraphicBufferProducer() const {
-    return mSink;
+    return mSource;
 }
 
 status_t VirtualDisplaySurface::compositionComplete() {
@@ -41,13 +48,49 @@
 }
 
 status_t VirtualDisplaySurface::advanceFrame() {
-    return NO_ERROR;
+    Mutex::Autolock lock(mMutex);
+    status_t result = NO_ERROR;
+
+    if (mAcquiredBuffer != NULL) {
+        result = mSource->releaseBuffer(mReleaseFence);
+        ALOGE_IF(result != NO_ERROR, "VirtualDisplaySurface \"%s\": "
+                "failed to release previous buffer: %d",
+                mName.string(), result);
+        mAcquiredBuffer.clear();
+        mReleaseFence = Fence::NO_FENCE;
+    }
+
+    sp<Fence> fence;
+    result = mSource->acquireBuffer(&mAcquiredBuffer, &fence);
+    if (result == BufferQueueInterposer::NO_BUFFER_AVAILABLE) {
+        result = mSource->pullEmptyBuffer();
+        if (result != NO_ERROR)
+            return result;
+        result = mSource->acquireBuffer(&mAcquiredBuffer, &fence);
+    }
+    if (result != NO_ERROR)
+        return result;
+
+    return mHwc.fbPost(mDisplayId, fence, mAcquiredBuffer);
 }
 
 status_t VirtualDisplaySurface::setReleaseFenceFd(int fenceFd) {
     if (fenceFd >= 0) {
-        ALOGW("[%s] unexpected release fence", mName.string());
-        close(fenceFd);
+        sp<Fence> fence(new Fence(fenceFd));
+        Mutex::Autolock lock(mMutex);
+        sp<Fence> mergedFence = Fence::merge(
+                String8::format("VirtualDisplaySurface \"%s\"",
+                        mName.string()),
+                mReleaseFence, fence);
+        if (!mergedFence->isValid()) {
+            ALOGE("VirtualDisplaySurface \"%s\": failed to merge release fence",
+                    mName.string());
+            // synchronization is broken, the best we can do is hope fences
+            // signal in order so the new fence will act like a union
+            mReleaseFence = fence;
+            return BAD_VALUE;
+        }
+        mReleaseFence = mergedFence;
     }
     return NO_ERROR;
 }
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index db886ed..66f8580 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 #define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 
-#include <utils/String8.h>
+#include "BufferQueueInterposer.h"
 #include "DisplaySurface.h"
 
 // ---------------------------------------------------------------------------
@@ -26,6 +26,28 @@
 
 class HWComposer;
 
+/* This DisplaySurface implementation uses a BufferQueueInterposer to pass
+ * partially- or fully-composited buffers from the OpenGL ES driver to
+ * HWComposer to use as the output buffer for virtual displays. Allowing HWC
+ * to compose into the same buffer that contains GLES results saves bandwidth
+ * compared to having two separate BufferQueues for frames with at least some
+ * GLES composition.
+ *
+ * The alternative would be to have two complete BufferQueues, one from GLES
+ * to HWC and one from HWC to the virtual display sink (e.g. video encoder).
+ * For GLES-only frames, the same bandwidth saving could be achieved if buffers
+ * could be acquired from the GLES->HWC queue and inserted into the HWC->sink
+ * queue. That would be complicated and doesn't help the mixed GLES+HWC case.
+ *
+ * On frames with no GLES composition, the VirtualDisplaySurface dequeues a
+ * buffer directly from the sink IGraphicBufferProducer and passes it to HWC,
+ * bypassing the GLES driver. This is only guaranteed to work if
+ * eglSwapBuffers doesn't immediately dequeue a buffer for the next frame,
+ * since we can't rely on being able to dequeue more than one buffer at a time.
+ *
+ * TODO(jessehall): Add a libgui test that ensures that EGL/GLES do lazy
+ * dequeBuffers; we've wanted to require that for other reasons anyway.
+ */
 class VirtualDisplaySurface : public DisplaySurface {
 public:
     VirtualDisplaySurface(HWComposer& hwc, int disp,
@@ -45,8 +67,13 @@
     // immutable after construction
     HWComposer& mHwc;
     int mDisplayId;
-    sp<IGraphicBufferProducer> mSink;
+    sp<BufferQueueInterposer> mSource;
     String8 mName;
+
+    // mutable, must be synchronized with mMutex
+    Mutex mMutex;
+    sp<GraphicBuffer> mAcquiredBuffer;
+    sp<Fence> mReleaseFence;
 };
 
 // ---------------------------------------------------------------------------