emugl: Introduce BufferQueue class.

This patch introduces a new class that will be used in a future
patch that refactors the RenderChannel interface and its
implementation.

BufferQueue implements a FIFO queue of RenderChannel::Buffer
instances, where each instance is protected from thread races
by an external mutex. This allows the caller to use several
instances at the same time without introducing hard
synchronization issues (e.g. thread races).

+ Add a new unit-test program, lib[64]OpenglRender_unittest
  that is run by android-rebuild.sh by default.

Change-Id: I39adf4aa4a19990b5138c9b2294c063d3bae4a78
diff --git a/android-rebuild.sh b/android-rebuild.sh
index 900a2ce..e01fd44 100755
--- a/android-rebuild.sh
+++ b/android-rebuild.sh
@@ -229,6 +229,7 @@
         for UNIT_TEST in android_emu_unittests emugl_common_host_unittests \
                          emulator_libui_unittests \
                          emulator_crashreport_unittests \
+                         libOpenglRender_unittests \
                          libGLcommon_unittests; do
             for TEST in $OUT_DIR/$UNIT_TEST$EXE_SUFFIX; do
                 echo "   - ${TEST#$OUT_DIR/}"
@@ -242,6 +243,7 @@
         for UNIT_TEST in android_emu64_unittests emugl64_common_host_unittests \
                          emulator64_libui_unittests \
                          emulator64_crashreport_unittests \
+                         lib64OpenglRender_unittests \
                          lib64GLcommon_unittests; do
              for TEST in $OUT_DIR/$UNIT_TEST$EXE_SUFFIX; do
                  echo "   - ${TEST#$OUT_DIR/}"
diff --git a/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h b/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
index 4f0b725..a6ee17a 100644
--- a/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
+++ b/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
@@ -46,6 +46,17 @@
         Stopped = 1 << 2,
     };
 
+    // Values corresponding to the result of i/o operations.
+    // |Ok| means everything went well.
+    // |TryAgain| means the operation could not be performed and should be
+    // tried later.
+    // |Error| means an error happened (i.e. the channel is stopped).
+    enum class IoResult {
+        Ok = 0,
+        TryAgain = 1,
+        Error = 2,
+    };
+
     // Possible points of origin for an event in EventCallback.
     enum class EventSource {
         RenderChannel,
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/Android.mk b/distrib/android-emugl/host/libs/libOpenglRender/Android.mk
index f165400..ca1a857 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/Android.mk
+++ b/distrib/android-emugl/host/libs/libOpenglRender/Android.mk
@@ -63,3 +63,13 @@
 $(call emugl-export,CFLAGS,$(EMUGL_USER_CFLAGS))
 
 $(call emugl-end-module)
+
+### OpenglRender unittests
+$(call emugl-begin-executable,lib$(BUILD_TARGET_SUFFIX)OpenglRender_unittests)
+
+LOCAL_SRC_FILES := \
+    BufferQueue_unittest.cpp \
+
+$(call emugl-import,lib$(BUILD_TARGET_SUFFIX)OpenglRender libemugl_gtest)
+$(call local-link-static-c++lib)
+$(call emugl-end-module)
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue.h b/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue.h
new file mode 100644
index 0000000..3a00a4b
--- /dev/null
+++ b/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2016 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.
+#pragma once
+
+#include "OpenglRender/RenderChannel.h"
+#include "android/base/Compiler.h"
+#include "android/base/synchronization/Lock.h"
+#include "android/base/synchronization/MessageChannel.h"
+
+#include <memory>
+
+#include <stddef.h>
+
+namespace emugl {
+
+// BufferQueue models a FIFO queue of RenderChannel::Buffer instances
+// that can be used between two different threads. Note that it depends,
+// for synchronization, on an external lock (passed as a reference in
+// the BufferQueue constructor).
+//
+// This allows one to use multiple BufferQueue instances whose content
+// are protected by a single lock.
+class BufferQueue {
+    using ConditionVariable = android::base::ConditionVariable;
+    using Lock = android::base::Lock;
+    using AutoLock = android::base::AutoLock;
+
+public:
+    using IoResult = RenderChannel::IoResult;
+    using Buffer = RenderChannel::Buffer;
+
+    // Constructor. |capacity| is the maximum number of Buffer instances in
+    // the queue, and |lock| is a reference to an external lock provided by
+    // the caller.
+    BufferQueue(size_t capacity, android::base::Lock& lock)
+        : mCapacity(capacity), mBuffers(new Buffer[capacity]), mLock(lock) {}
+
+    // Return true iff the queue has been closed. Remaining items can
+    // be read from its, but it won't be possible to add new items to it.
+    // bool closed() const { return mClosed; }
+
+    // Return true iff one can send a buffer to the queue, i.e. if it
+    // is not full.
+    bool canPushLocked() const { return !mClosed && (mCount < mCapacity); }
+
+    // Return true iff one can receive a buffer from the queue, i.e. if
+    // it is not empty.
+    bool canPopLocked() const { return mCount > 0U; }
+
+    // Return true iff the queue is closed.
+    bool isClosedLocked() const { return mClosed; }
+
+    // Try to send a buffer to the queue. On success, return IoResult::Ok
+    // and moves |buffer| to the queue. On failure, return
+    // IoResult::TryAgain if the queue was full, or IoResult::Error
+    // if it was closed.
+    IoResult tryPushLocked(Buffer&& buffer) {
+        if (mClosed) {
+            return IoResult::Error;
+        }
+        if (mCount >= mCapacity) {
+            return IoResult::TryAgain;
+        }
+        size_t pos = mPos + mCount;
+        if (pos >= mCapacity) {
+            pos -= mCapacity;
+        }
+        mBuffers[pos] = std::move(buffer);
+        if (mCount++ == 0) {
+            mCanPop.signal();
+        }
+        return IoResult::Ok;
+    }
+
+    // Push a buffer to the queue. This is a blocking call. On success,
+    // move |buffer| into the queue and return IoResult::Ok. On failure,
+    // return IoResult::Error meaning the queue was closed.
+    IoResult pushLocked(Buffer&& buffer) {
+        while (mCount == mCapacity) {
+            if (mClosed) {
+                return IoResult::Error;
+            }
+            mCanPush.wait(&mLock);
+        }
+        return tryPushLocked(std::move(buffer));
+    }
+
+    // Try to read a buffer from the queue. On success, moves item into
+    // |*buffer| and return IoResult::Ok. On failure, return IoResult::Error
+    // if the queue is empty and closed, and IoResult::TryAgain if it is
+    // empty but not close.
+    IoResult tryPopLocked(Buffer* buffer) {
+        if (mCount == 0) {
+            return mClosed ? IoResult::Error : IoResult::TryAgain;
+        }
+        *buffer = std::move(mBuffers[mPos]);
+        size_t pos = mPos + 1;
+        if (pos >= mCapacity) {
+            pos -= mCapacity;
+        }
+        mPos = pos;
+        if (mCount-- == mCapacity) {
+            mCanPush.signal();
+        }
+        return IoResult::Ok;
+    }
+
+    // Pop a buffer from the queue. This is a blocking call. On success,
+    // move item into |*buffer| and return IoResult::Ok. On failure,
+    // return IoResult::Error to indicate the queue was closed.
+    IoResult popLocked(Buffer* buffer) {
+        while (mCount == 0) {
+            if (mClosed) {
+                // Closed queue is empty.
+                return IoResult::Error;
+            }
+            mCanPop.wait(&mLock);
+        }
+        return tryPopLocked(buffer);
+    }
+
+    // Close the queue, it is no longer possible to push new items
+    // to it (i.e. push() will always return IoResult::Error), or to
+    // read from an empty queue (i.e. pop() will always return
+    // IoResult::Error once the queue becomes empty).
+    void closeLocked() {
+        mClosed = true;
+
+        // Wake any potential waiters.
+        if (mCount == mCapacity) {
+            mCanPush.broadcast();
+        }
+        if (mCount == 0) {
+            mCanPop.broadcast();
+        }
+    }
+
+private:
+    size_t mCapacity = 0;
+    size_t mPos = 0;
+    size_t mCount = 0;
+    bool mClosed = false;
+    std::unique_ptr<Buffer[]> mBuffers;
+
+    Lock& mLock;
+    ConditionVariable mCanPush;
+    ConditionVariable mCanPop;
+
+    // This will force the same for SyncBufferQueue and RenderChannelImpl
+    DISALLOW_COPY_ASSIGN_AND_MOVE(BufferQueue);
+};
+
+}  // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue_unittest.cpp b/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue_unittest.cpp
new file mode 100644
index 0000000..778a766
--- /dev/null
+++ b/distrib/android-emugl/host/libs/libOpenglRender/BufferQueue_unittest.cpp
@@ -0,0 +1,328 @@
+// Copyright (C) 2016 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.
+
+#include "BufferQueue.h"
+
+#ifdef _WIN32
+#undef ERROR   // Make LOG(ERROR) compile properly.
+#endif
+
+#include "android/base/Log.h"
+#include "android/base/synchronization/Lock.h"
+#include "android/base/synchronization/MessageChannel.h"
+#include "android/base/threads/Thread.h"
+#include "OpenglRender/RenderChannel.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+using android::base::Lock;
+using android::base::AutoLock;
+using Buffer = BufferQueue::Buffer;
+using IoResult = BufferQueue::IoResult;
+
+TEST(BufferQueue, Constructor) {
+    Lock lock;
+    BufferQueue queue(16, lock);
+}
+
+TEST(BufferQueue, tryPushLocked) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    AutoLock al(lock);
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
+
+    Buffer buff0("You Shall Not Move");
+    EXPECT_EQ(IoResult::TryAgain, queue.tryPushLocked(std::move(buff0)));
+    EXPECT_FALSE(buff0.empty()) << "Buffer should not be moved on failure!";
+}
+
+TEST(BufferQueue, tryPushLockedOnClosedQueue) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    AutoLock al(lock);
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
+
+    // Closing the queue prevents pushing new items to the queue.
+    queue.closeLocked();
+
+    EXPECT_EQ(IoResult::Error, queue.tryPushLocked(Buffer("World")));
+}
+
+TEST(BufferQueue, tryPopLocked) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    AutoLock al(lock);
+
+    Buffer buffer;
+    EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("Hello", buffer.data());
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("World", buffer.data());
+
+    EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("World", buffer.data());
+}
+
+TEST(BufferQueue, tryPopLockedOnClosedQueue) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    AutoLock al(lock);
+
+    Buffer buffer;
+    EXPECT_EQ(IoResult::TryAgain, queue.tryPopLocked(&buffer));
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("Hello")));
+    EXPECT_EQ(IoResult::Ok, queue.tryPushLocked(Buffer("World")));
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("Hello", buffer.data());
+
+    // Closing the queue doesn't prevent popping existing items, but
+    // will generate IoResult::Error once it is empty.
+    queue.closeLocked();
+
+    EXPECT_EQ(IoResult::Ok, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("World", buffer.data());
+
+    EXPECT_EQ(IoResult::Error, queue.tryPopLocked(&buffer));
+    EXPECT_STREQ("World", buffer.data());
+}
+
+namespace {
+
+// A TestThread instance that holds a reference to a queue and can either
+// push or pull to it, on command from another thread. This uses a
+// MessageChannel to implement the communication channel between the
+// command thread and this one.
+class TestThread final : public android::base::Thread {
+public:
+    TestThread(Lock& lock, BufferQueue& queue)
+        // NOTE: The default stack size of android::base::Thread is too
+        //       small and will result in runtime errors.
+        : mLock(lock), mQueue(queue) {}
+
+    // Tell the test thread to push |buffer| to the queue.
+    // Call endPush() later to get the command's result.
+    bool startPush(Buffer&& buffer) {
+        return mInput.send(
+                Request { .cmd = Cmd::Push, .buffer = std::move(buffer) });
+    }
+
+    // Get the result of a previous startPush() command.
+    IoResult endPush() {
+        Reply reply = {};
+        if (!mOutput.receive(&reply)) {
+            return IoResult::Error;
+        }
+        return reply.result;
+    }
+
+    // Tell the test thread to pop a buffer from the queue.
+    // Call endPop() to get the command's result, as well as the popped
+    // buffer if it is IoResult::Ok.
+    bool startPop() {
+        return mInput.send(Request { .cmd = Cmd::Pop });
+    }
+
+    // Return the result of a previous startPop() command. If result is
+    // IoResult::Ok, sets |*buffer| to the result buffer.
+    IoResult endPop(Buffer* buffer) {
+        Reply reply = {};
+        if (!mOutput.receive(&reply)) {
+            return IoResult::Error;
+        }
+        if (reply.result == IoResult::Ok) {
+            *buffer = std::move(reply.buffer);
+        }
+        return reply.result;
+    }
+
+    // Tell the test thread to close the queue from its side.
+    void doClose() {
+        mInput.send(Request { .cmd = Cmd::Close });
+    }
+
+    // Tell the test thread to stop after completing its current command.
+    void stop() {
+        mInput.send(Request { .cmd = Cmd::Stop });
+        ASSERT_TRUE(this->wait());
+    }
+
+private:
+    enum class Cmd {
+        Push,
+        Pop,
+        Close,
+        Stop,
+    };
+
+    struct Request {
+        Cmd cmd;
+        Buffer buffer;
+    };
+
+    struct Reply {
+        IoResult result;
+        Buffer buffer;
+    };
+
+    // Main thread function.
+    virtual intptr_t main() override final {
+        for (;;) {
+            Request r;
+            if (!mInput.receive(&r)) {
+                LOG(ERROR) << "Could not receive command";
+                break;
+            }
+            if (r.cmd == Cmd::Stop) {
+                break;
+            }
+            mLock.lock();
+            Reply reply = {};
+            bool sendReply = false;
+            switch (r.cmd) {
+                case Cmd::Push:
+                    reply.result = mQueue.pushLocked(std::move(r.buffer));
+                    sendReply = true;
+                    break;
+
+                case Cmd::Pop:
+                    reply.result = mQueue.popLocked(&reply.buffer);
+                    sendReply = true;
+                    break;
+
+                case Cmd::Close:
+                    mQueue.closeLocked();
+                    break;
+
+                default:
+                    ;
+            }
+            mLock.unlock();
+            if (sendReply) {
+                if (!mOutput.send(std::move(reply))) {
+                    LOG(ERROR) << "Could not send reply";
+                    break;
+                }
+            }
+        }
+        return 0U;
+    }
+
+    Lock& mLock;
+    BufferQueue& mQueue;
+    android::base::MessageChannel<Request, 4> mInput;
+    android::base::MessageChannel<Reply, 4> mOutput;
+};
+
+}  // namespace
+
+TEST(BufferQueue, pushLocked) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    TestThread thread(lock, queue);
+
+    ASSERT_TRUE(thread.start());
+    ASSERT_TRUE(thread.startPop());
+
+    lock.lock();
+    EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Hello")));
+    EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("World")));
+    EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Foo")));
+    lock.unlock();
+
+    thread.stop();
+}
+
+TEST(BufferQueue, pushLockedWithClosedQueue) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    TestThread thread(lock, queue);
+
+    ASSERT_TRUE(thread.start());
+
+    lock.lock();
+    EXPECT_EQ(IoResult::Ok, queue.pushLocked(Buffer("Hello")));
+    // Closing the queue prevents pushing new items, but not
+    // pulling from the queue.
+    queue.closeLocked();
+    EXPECT_EQ(IoResult::Error, queue.pushLocked(Buffer("World")));
+    lock.unlock();
+
+    Buffer buffer;
+    ASSERT_TRUE(thread.startPop());
+    EXPECT_EQ(IoResult::Ok, thread.endPop(&buffer));
+    EXPECT_STREQ("Hello", buffer.data());
+
+    thread.stop();
+}
+
+TEST(BufferQueue, popLocked) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    TestThread thread(lock, queue);
+
+    ASSERT_TRUE(thread.start());
+    ASSERT_TRUE(thread.startPush(Buffer("Hello World")));
+    EXPECT_EQ(IoResult::Ok, thread.endPush());
+
+    lock.lock();
+    Buffer buffer;
+    EXPECT_EQ(IoResult::Ok, queue.popLocked(&buffer));
+    EXPECT_STREQ("Hello World", buffer.data());
+    lock.unlock();
+
+    thread.stop();
+}
+
+TEST(BufferQueue, popLockedWithClosedQueue) {
+    Lock lock;
+    BufferQueue queue(2, lock);
+    TestThread thread(lock, queue);
+
+    ASSERT_TRUE(thread.start());
+    ASSERT_TRUE(thread.startPush(Buffer("Hello World")));
+    EXPECT_EQ(IoResult::Ok, thread.endPush());
+
+    // Closing the queue shall not prevent pulling items from it.
+    // After that, IoResult::Error shall be returned.
+    thread.doClose();
+
+    ASSERT_TRUE(thread.startPush(Buffer("Foo Bar")));
+    EXPECT_EQ(IoResult::Error, thread.endPush());
+
+    lock.lock();
+    Buffer buffer;
+    EXPECT_EQ(IoResult::Ok, queue.popLocked(&buffer));
+    EXPECT_STREQ("Hello World", buffer.data());
+
+    EXPECT_EQ(IoResult::Error, queue.popLocked(&buffer));
+    EXPECT_STREQ("Hello World", buffer.data());
+    lock.unlock();
+
+    thread.stop();
+}
+
+}  // namespace emugl