Merge changes I1f0e998b,I39adf4aa,I5b8ea4e2,I02928be0,I75b20025, ... into emu-master-dev
* changes:
emugl: Simplify RenderChannel interface.
emugl: Introduce BufferQueue class.
emugl: ChannelBuffer -> RenderChannel::Buffer
emugl: put ChannelStream/ReadBuffer/RenderThread in emugl namespace.
emugl: Move IOStream.h to host/include/libOpenglRender/
emugl: Add emugl/common/debug.h
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/android/opengl/OpenglEsPipe.cpp b/android/opengl/OpenglEsPipe.cpp
index 35aafff..9e0491c 100644
--- a/android/opengl/OpenglEsPipe.cpp
+++ b/android/opengl/OpenglEsPipe.cpp
@@ -22,7 +22,7 @@
#include <string.h>
// Set to 1 or 2 for debug traces
-//#define DEBUG 1
+#define DEBUG 0
#if DEBUG >= 1
#define D(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
@@ -36,10 +36,11 @@
#define DD(...) ((void)0)
#endif
-using emugl::ChannelBuffer;
+using ChannelBuffer = emugl::RenderChannel::Buffer;
using emugl::RenderChannel;
using emugl::RenderChannelPtr;
using ChannelState = emugl::RenderChannel::State;
+using IoResult = emugl::RenderChannel::IoResult;
namespace android {
namespace opengl {
@@ -79,8 +80,7 @@
/////////////////////////////////////////////////////////////////////////
// Constructor, check that |mIsWorking| is true after this call to verify
// that everything went well.
- EmuglPipe(void* hwPipe,
- Service* service,
+ EmuglPipe(void* hwPipe, Service* service,
const emugl::RendererPtr& renderer)
: AndroidPipe(hwPipe, service) {
mChannel = renderer->createRenderChannel();
@@ -90,10 +90,10 @@
}
mIsWorking = true;
- mChannel->setEventCallback([this](ChannelState state,
- RenderChannel::EventSource source) {
- this->onChannelIoEvent(state);
- });
+ mChannel->setEventCallback(
+ [this](RenderChannel::State events) {
+ this->onChannelHostEvent(events);
+ });
}
//////////////////////////////////////////////////////////////////////////
@@ -103,20 +103,27 @@
D("%s", __func__);
mIsWorking = false;
mChannel->stop();
- // stop callback will call close() which deletes the pipe
+ delete this;
}
virtual unsigned onGuestPoll() const override {
DD("%s", __func__);
unsigned ret = 0;
- ChannelState state = mChannel->currentState();
- if (canReadAny(state)) {
+ if (mDataForReadingLeft > 0) {
ret |= PIPE_POLL_IN;
}
- if (canWrite(state)) {
+ ChannelState state = mChannel->state();
+ if ((state & ChannelState::CanRead) != 0) {
+ ret |= PIPE_POLL_IN;
+ }
+ if ((state & ChannelState::CanWrite) != 0) {
ret |= PIPE_POLL_OUT;
}
+ if ((state & ChannelState::Stopped) != 0) {
+ ret |= PIPE_POLL_HUP;
+ }
+ DD("%s: returning %d", __func__, ret);
return ret;
}
@@ -124,8 +131,8 @@
override {
DD("%s", __func__);
- // Consume the pipe's dataForReading, then put the next received data piece
- // there. Repeat until the buffers are full or we're out of data
+ // Consume the pipe's dataForReading, then put the next received data
+ // piece there. Repeat until the buffers are full or we're out of data
// in the channel.
int len = 0;
size_t buffOffset = 0;
@@ -135,29 +142,37 @@
while (buff != buffEnd) {
if (mDataForReadingLeft == 0) {
// No data left, read a new chunk from the channel.
- //
- // Spin a little bit here: many GL calls are much faster than
- // the whole host-to-guest-to-host transition.
int spinCount = 20;
- while (!mChannel->read(&mDataForReading,
- RenderChannel::CallType::Nonblocking)) {
- if (mChannel->isStopped()) {
- return PIPE_ERROR_IO;
- } else if (len == 0) {
- if (canRead(mChannel->currentState()) || --spinCount > 0) {
- continue;
- }
- return PIPE_ERROR_AGAIN;
- } else {
+ for (;;) {
+ auto result = mChannel->tryRead(&mDataForReading);
+ if (result == IoResult::Ok) {
+ mDataForReadingLeft = mDataForReading.size();
+ break;
+ }
+ DD("%s: tryRead() failed with %d", __func__, (int)result);
+ // This failed either because the channel was stopped
+ // from the host, or if there was no data yet in the
+ // channel.
+ if (len > 0) {
+ DD("%s: returning %d bytes", __func__, (int)len);
return len;
}
+ if (result == IoResult::Error) {
+ return PIPE_ERROR_IO;
+ }
+ // Spin a little before declaring there is nothing
+ // to read. Many GL calls are much faster than the
+ // whole host-to-guest-to-host transition.
+ if (--spinCount > 0) {
+ continue;
+ }
+ DD("%s: returning PIPE_ERROR_AGAIN", __func__);
+ return PIPE_ERROR_AGAIN;
}
-
- mDataForReadingLeft = mDataForReading.size();
}
const size_t curSize =
- std::min(buff->size - buffOffset, mDataForReadingLeft.load());
+ std::min(buff->size - buffOffset, mDataForReadingLeft);
memcpy(buff->data + buffOffset,
mDataForReading.data() +
(mDataForReading.size() - mDataForReadingLeft),
@@ -172,6 +187,7 @@
}
}
+ DD("%s: received %d bytes", __func__, (int)len);
return len;
}
@@ -179,8 +195,9 @@
int numBuffers) override {
DD("%s", __func__);
- if (int ret = sendReadyStatus()) {
- return ret;
+ if (!mIsWorking) {
+ DD("%s: pipe already closed!", __func__);
+ return PIPE_ERROR_IO;
}
// Count the total bytes to send.
@@ -198,9 +215,12 @@
ptr += buffers[n].size;
}
+ D("%s: sending %d bytes to host", __func__, count);
// Send it through the channel.
- if (!mChannel->write(std::move(outBuffer))) {
- return PIPE_ERROR_IO;
+ auto result = mChannel->tryWrite(std::move(outBuffer));
+ if (result != IoResult::Ok) {
+ D("%s: tryWrite() failed with %d", __func__, (int)result);
+ return result == IoResult::Error ? PIPE_ERROR_IO : PIPE_ERROR_AGAIN;
}
return count;
@@ -209,112 +229,66 @@
virtual void onGuestWantWakeOn(int flags) override {
DD("%s: flags=%d", __func__, flags);
- // We reset these flags when we actually wake the pipe, so only
- // add to them here.
- mCareAboutRead |= (flags & PIPE_WAKE_READ) != 0;
- mCareAboutWrite |= (flags & PIPE_WAKE_WRITE) != 0;
+ // Translate |flags| into ChannelState flags.
+ ChannelState wanted = ChannelState::Empty;
+ if (flags & PIPE_WAKE_READ) {
+ wanted |= ChannelState::CanRead;
+ }
+ if (flags & PIPE_WAKE_WRITE) {
+ wanted |= ChannelState::CanWrite;
+ }
- ChannelState state = mChannel->currentState();
- processIoEvents(state);
+ // Signal events that are already available now.
+ ChannelState state = mChannel->state();
+ ChannelState available = state & wanted;
+ DD("%s: state=%d wanted=%d available=%d", __func__, (int)state,
+ (int)wanted, (int)available);
+ if (available != ChannelState::Empty) {
+ DD("%s: signaling events %d", __func__, (int)available);
+ signalState(available);
+ wanted &= ~available;
+ }
+
+ // Ask the channel to be notified of remaining events.
+ if (wanted != ChannelState::Empty) {
+ DD("%s: waiting for events %d", __func__, (int)wanted);
+ mChannel->setWantedEvents(wanted);
+ }
}
private:
- // Returns true iff there is data to read from the emugl channel.
- static bool canRead(ChannelState state) {
- return (state & ChannelState::CanRead) != ChannelState::Empty;
- }
-
- // Returns true iff the emugl channel can accept data.
- static bool canWrite(ChannelState state) {
- return (state & ChannelState::CanWrite) != ChannelState::Empty;
- }
-
- // Returns true iff there is data to read from the local cache or from
- // the emugl channel.
- bool canReadAny(ChannelState state) const {
- return mDataForReadingLeft != 0 || canRead(state);
- }
-
- // Check that the pipe is working and that the render channel can be
- // written to. Return 0 in case of success, and one PIPE_ERROR_XXX
- // value on error.
- int sendReadyStatus() const {
- if (mIsWorking) {
- ChannelState state = mChannel->currentState();
- if (canWrite(state)) {
- return 0;
- }
- return PIPE_ERROR_AGAIN;
- } else if (!mHwPipe) {
- return PIPE_ERROR_IO;
- } else {
- return PIPE_ERROR_INVAL;
- }
- }
-
- // Check the read/write state of the render channel and signal the pipe
- // if any condition meets mCareAboutRead or mCareAboutWrite.
- void processIoEvents(ChannelState state) {
+ // Called to signal the guest that read/write wake events occured.
+ // Note: this can be called from either the guest or host render
+ // thread.
+ void signalState(ChannelState state) {
int wakeFlags = 0;
-
- if (mCareAboutRead && canReadAny(state)) {
+ if ((state & ChannelState::CanRead) != 0) {
wakeFlags |= PIPE_WAKE_READ;
- mCareAboutRead = false;
}
- if (mCareAboutWrite && canWrite(state)) {
+ if ((state & ChannelState::CanWrite) != 0) {
wakeFlags |= PIPE_WAKE_WRITE;
- mCareAboutWrite = false;
}
-
- // Send wake signal to the guest if needed.
if (wakeFlags != 0) {
- signalWake(wakeFlags);
+ this->signalWake(wakeFlags);
}
}
// Called when an i/o event occurs on the render channel
- void onChannelIoEvent(ChannelState state) {
- D("%s: %d", __func__, (int)state);
-
- if ((state & ChannelState::Stopped) != ChannelState::Empty) {
- close();
- } else {
- processIoEvents(state);
- }
- }
-
- // Close the pipe, this may be called from the host or guest side.
- void close() {
- D("%s", __func__);
-
- // If the pipe isn't in a working state, delete immediately.
- if (!mIsWorking) {
- mChannel->stop();
- delete this;
+ void onChannelHostEvent(ChannelState state) {
+ D("%s: events %d", __func__, (int)state);
+ // NOTE: This is called from the host-side render thread.
+ // but closeFromHost() and signalWake() can be called from
+ // any thread.
+ if ((state & ChannelState::Stopped) != 0) {
+ this->closeFromHost();
return;
}
-
- // Force the closure of the channel - if a guest is blocked
- // waiting for a wake signal, it will receive an error.
- if (mHwPipe) {
- closeFromHost();
- mHwPipe = nullptr;
- }
-
- mIsWorking = false;
+ signalState(state);
}
// A RenderChannel pointer used for communication.
RenderChannelPtr mChannel;
- // Guest state tracking - if it requested us to wake on read/write
- // availability. If guest doesn't care about some operation type, we should
- // not wake it when that operation becomes available.
- // Note: we need an atomic or operation, and atomic<bool> doesn't have it -
- // that's why it is atomic<char> here.
- std::atomic<char> mCareAboutRead {false};
- std::atomic<char> mCareAboutWrite {false};
-
// Set to |true| if the pipe is in working state, |false| means we're not
// initialized or the pipe is closed.
bool mIsWorking = false;
@@ -326,7 +300,7 @@
// If guest didn't have enough room for the whole buffer, we track the
// number of remaining bytes in |mDataForReadingLeft| for the next read().
ChannelBuffer mDataForReading;
- std::atomic<size_t> mDataForReadingLeft { 0 };
+ size_t mDataForReadingLeft = 0;
DISALLOW_COPY_ASSIGN_AND_MOVE(EmuglPipe);
};
diff --git a/distrib/android-emugl/DESIGN b/distrib/android-emugl/DESIGN
index 943a0e4..ceb8331 100644
--- a/distrib/android-emugl/DESIGN
+++ b/distrib/android-emugl/DESIGN
@@ -301,7 +301,7 @@
The "IOStream" is a very simple abstract class used to send byte messages
both in the guest and host. It is defined through a shared header under
-$EMUGL/host/include/libOpenglRender/IOStream.h
+$EMUGL/host/include/OpenglRender/IOStream.h
Note that despite the path, this header is included by *both* host and guest
source code. The main idea around IOStream's design is that to send a message,
diff --git a/distrib/android-emugl/host/include/libOpenglRender/IOStream.h b/distrib/android-emugl/host/include/OpenglRender/IOStream.h
similarity index 94%
rename from distrib/android-emugl/host/include/libOpenglRender/IOStream.h
rename to distrib/android-emugl/host/include/OpenglRender/IOStream.h
index a0df579..46b8c6a 100644
--- a/distrib/android-emugl/host/include/libOpenglRender/IOStream.h
+++ b/distrib/android-emugl/host/include/OpenglRender/IOStream.h
@@ -33,7 +33,7 @@
public:
size_t read(void* buf, size_t bufLen) {
- if (!read(buf, &bufLen)) {
+ if (!readRaw(buf, &bufLen)) {
return 0;
}
return bufLen;
@@ -75,7 +75,7 @@
protected:
virtual void *allocBuffer(size_t minSize) = 0;
virtual int commitBuffer(size_t size) = 0;
- virtual const unsigned char *read(void *buf, size_t *inout_len) = 0;
+ virtual const unsigned char *readRaw(void *buf, size_t *inout_len) = 0;
private:
unsigned char* m_buf = nullptr;
diff --git a/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h b/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
index 6c139c3..40232e2 100644
--- a/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
+++ b/distrib/android-emugl/host/include/OpenglRender/RenderChannel.h
@@ -21,18 +21,35 @@
namespace emugl {
-// Turn the RenderChannel::Event enum into flags.
+// Turn the RenderChannel::State enum into flags.
using namespace ::android::base::EnumFlags;
-// A type used for data passing.
-using ChannelBuffer = android::base::SmallFixedVector<char, 512>;
-
-// RenderChannel - an interface for a single guest to host renderer connection.
-// It allows the guest to send GPU emulation protocol-serialized messages to an
-// asynchronous renderer, read the responses and subscribe for state updates.
+// RenderChannel - For each guest-to-host renderer connection, this provides
+// an interface for the guest side to interact with the corresponding renderer
+// thread on the host. Its main purpose is to send and receive wire protocol
+// bytes in an asynchronous way (compatible with Android pipes).
+//
+// Usage is the following:
+// 1) Get an instance pointer through a dedicated Renderer function
+// (e.g. RendererImpl::createRenderChannel()).
+//
+// 2) Call setEventCallback() to indicate which callback should be called
+// when the channel's state has changed due to a host thread event.
+//
class RenderChannel {
public:
- // Flags for the channel state.
+ // A type used to pass byte packets between the guest and the
+ // RenderChannel instance. Experience has shown that using a
+ // SmallFixedVector<char, N> instance instead of a std::vector<char>
+ // avoids a lot of un-necessary heap allocations. The current size
+ // of 512 was selected after profiling existing traffic, including
+ // the one used in protocol-heavy benchmark like Antutu3D.
+ using Buffer = android::base::SmallFixedVector<char, 512>;
+
+ // Bit-flags for the channel state.
+ // |CanRead| means there is data from the host to read.
+ // |CanWrite| means there is room to send data to the host.
+ // |Stopped| means the channel was stopped.
enum class State {
// Can't use None here, some system header declares it as a macro.
Empty = 0,
@@ -41,47 +58,60 @@
Stopped = 1 << 2,
};
- // Possible points of origin for an event in EventCallback.
- enum class EventSource {
- RenderChannel,
- Client,
+ // 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,
};
- // Types of read() the channel supports.
- enum class CallType {
- Blocking, // if the call can't do what it needs, block until it can
- Nonblocking, // immidiately return if the call can't do the job
- };
+ // Type of a callback used to tell the guest when the RenderChannel
+ // state changes. Used by setEventCallback(). The parameter contains
+ // the State bits matching the event, i.e. it is the logical AND of
+ // the last value passed to setWantedEvents() and the current
+ // RenderChannel state.
+ using EventCallback = std::function<void(State)>;
- // Sets a single (!) callback that is called if some event happends that
- // changes the channel state - e.g. when it's stopped, or it gets some data
- // the client can read after being empty, or it isn't full anymore and the
- // client may write again without blocking.
- // If the state isn't State::Empty, the |callback| is called for the first
- // time during the setEventCallback() to report this initial state.
- using EventCallback = std::function<void(State, EventSource)>;
- virtual void setEventCallback(EventCallback callback) = 0;
+ // Sets a single (!) callback that is called when the channel state's
+ // changes due to an event *from* *the* *host* only. |callback| is a
+ // guest-provided callback that will be called from the host renderer
+ // thread, not the guest one.
+ virtual void setEventCallback(EventCallback&& callback) = 0;
- // Writes the data in |buffer| into the channel. |buffer| is moved from.
- // Blocks if there's no room in the channel (shouldn't really happen).
- // Returns false if the channel is stopped.
- virtual bool write(ChannelBuffer&& buffer) = 0;
- // Reads a chunk of data from the channel. Returns false if there was no
- // data for a non-blocking call or if the channel is stopped.
- virtual bool read(ChannelBuffer* buffer, CallType callType) = 0;
+ // Used to indicate which i/o events the guest wants to be notified
+ // through its StateChangeCallback. |state| must be a combination of
+ // State::CanRead or State::CanWrite only. This will *not* call the
+ // callback directly since this happens in the guest thread.
+ virtual void setWantedEvents(State state) = 0;
// Get the current state flags.
- virtual State currentState() const = 0;
+ virtual State state() const = 0;
+
+ // Try to writes the data in |buffer| into the channel. On success,
+ // return IoResult::Ok and moves |buffer|. On failure, return
+ // IoResult::TryAgain if the channel was full, or IoResult::Error
+ // if it is stopped.
+ virtual IoResult tryWrite(Buffer&& buffer) = 0;
+
+ // Try to read data from the channel. On success, return IoResult::Ok and
+ // sets |*buffer| to contain the data. On failure, return
+ // IoResult::TryAgain if the channel was empty, or IoResult::Error if
+ // it was stopped.
+ virtual IoResult tryRead(Buffer* buffer) = 0;
// Abort all pending operations. Any following operation is a noop.
+ // Once a channel is stopped, it cannot be re-started.
virtual void stop() = 0;
- // Check if the channel is stopped.
- virtual bool isStopped() const = 0;
protected:
~RenderChannel() = default;
};
+// Shared pointer to RenderChannel instance.
using RenderChannelPtr = std::shared_ptr<RenderChannel>;
} // namespace emugl
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
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.cpp b/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.cpp
index db0589b..0f402b9 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.cpp
+++ b/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.cpp
@@ -13,42 +13,81 @@
// limitations under the License.
#include "ChannelStream.h"
-#include <assert.h>
+#include "OpenglRender/RenderChannel.h"
-ChannelStream::ChannelStream(std::shared_ptr<emugl::RenderChannelImpl> channel,
+#define EMUGL_DEBUG_LEVEL 0
+#include "emugl/common/debug.h"
+
+#include <assert.h>
+#include <memory.h>
+
+namespace emugl {
+
+using IoResult = RenderChannel::IoResult;
+
+ChannelStream::ChannelStream(std::shared_ptr<RenderChannelImpl> channel,
size_t bufSize)
: IOStream(bufSize), mChannel(channel) {
- mBuf.resize_noinit(bufSize);
+ mWriteBuffer.resize_noinit(bufSize);
}
void* ChannelStream::allocBuffer(size_t minSize) {
- if (mBuf.size() < minSize) {
- mBuf.resize_noinit(minSize);
+ if (mWriteBuffer.size() < minSize) {
+ mWriteBuffer.resize_noinit(minSize);
}
- return mBuf.data();
+ return mWriteBuffer.data();
}
int ChannelStream::commitBuffer(size_t size) {
- assert(size <= mBuf.size());
- if (mBuf.isAllocated()) {
- mBuf.resize(size);
- mChannel->writeToGuest(std::move(mBuf));
+ assert(size <= mWriteBuffer.size());
+ if (mWriteBuffer.isAllocated()) {
+ mWriteBuffer.resize(size);
+ mChannel->writeToGuest(std::move(mWriteBuffer));
} else {
mChannel->writeToGuest(
- emugl::ChannelBuffer(mBuf.data(), mBuf.data() + size));
+ RenderChannel::Buffer(mWriteBuffer.data(), mWriteBuffer.data() + size));
}
return size;
}
-const unsigned char* ChannelStream::read(void* buf, size_t* inout_len) {
- size_t size = mChannel->readFromGuest((char*)buf, *inout_len, true);
- if (size == 0) {
+const unsigned char* ChannelStream::readRaw(void* buf, size_t* inout_len) {
+ size_t wanted = *inout_len;
+ size_t count = 0U;
+ auto dst = static_cast<uint8_t*>(buf);
+ D("wanted %d bytes", (int)wanted);
+ while (count < wanted) {
+ if (mReadBufferLeft > 0) {
+ size_t avail = std::min<size_t>(wanted - count, mReadBufferLeft);
+ memcpy(dst + count,
+ mReadBuffer.data() + (mReadBuffer.size() - mReadBufferLeft),
+ avail);
+ count += avail;
+ mReadBufferLeft -= avail;
+ continue;
+ }
+ bool blocking = (count == 0);
+ auto result = mChannel->readFromGuest(&mReadBuffer, blocking);
+ D("readFromGuest() returned %d, size %d", (int)result, (int)mReadBuffer.size());
+ if (result == IoResult::Ok) {
+ mReadBufferLeft = mReadBuffer.size();
+ continue;
+ }
+ if (count > 0) { // There is some data to return.
+ break;
+ }
+ // Result can only be IoResult::Error if |count == 0| since |blocking|
+ // was true, it cannot be IoResult::TryAgain.
+ assert(result == IoResult::Error);
+ D("error while trying to read");
return nullptr;
}
- *inout_len = size;
+ *inout_len = count;
+ D("read %d bytes", (int)count);
return (const unsigned char*)buf;
}
void ChannelStream::forceStop() {
- mChannel->stop();
+ mChannel->stopFromHost();
}
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.h b/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.h
index 0a2d5f4..02b2fe7 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.h
+++ b/distrib/android-emugl/host/libs/libOpenglRender/ChannelStream.h
@@ -13,25 +13,32 @@
// limitations under the License.
#pragma once
-#include "IOStream.h"
+#include "OpenglRender/IOStream.h"
#include "RenderChannelImpl.h"
#include <memory>
-#include <vector>
+namespace emugl {
+
+// An IOStream instance that can be used by the host RenderThread to
+// wrap a RenderChannelImpl channel.
class ChannelStream final : public IOStream {
public:
- ChannelStream(std::shared_ptr<emugl::RenderChannelImpl> channel,
- size_t bufSize);
-
- virtual void* allocBuffer(size_t minSize) override final;
- virtual int commitBuffer(size_t size) override final;
- virtual const unsigned char* read(void* buf,
- size_t* inout_len) override final;
+ ChannelStream(std::shared_ptr<RenderChannelImpl> channel, size_t bufSize);
void forceStop();
+protected:
+ virtual void* allocBuffer(size_t minSize) override final;
+ virtual int commitBuffer(size_t size) override final;
+ virtual const unsigned char* readRaw(void* buf, size_t* inout_len)
+ override final;
+
private:
- std::shared_ptr<emugl::RenderChannelImpl> mChannel;
- emugl::ChannelBuffer mBuf;
+ std::shared_ptr<RenderChannelImpl> mChannel;
+ RenderChannel::Buffer mWriteBuffer;
+ RenderChannel::Buffer mReadBuffer;
+ size_t mReadBufferLeft = 0;
};
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.cpp b/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.cpp
index 9f616b8..46851a3 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.cpp
+++ b/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.cpp
@@ -23,6 +23,8 @@
#include <string.h>
#include <limits.h>
+namespace emugl {
+
ReadBuffer::ReadBuffer(size_t bufsize) {
m_size = bufsize;
m_buf = (unsigned char*)malloc(m_size);
@@ -97,3 +99,5 @@
m_validData -= amount;
m_readPtr += amount;
}
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.h b/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.h
index c302e79..f3472ac 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.h
+++ b/distrib/android-emugl/host/libs/libOpenglRender/ReadBuffer.h
@@ -14,7 +14,9 @@
* limitations under the License.
*/
#pragma once
-#include "IOStream.h"
+#include "OpenglRender/IOStream.h"
+
+namespace emugl {
class ReadBuffer {
public:
@@ -30,3 +32,5 @@
size_t m_size;
size_t m_validData;
};
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.cpp b/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.cpp
index cd8b979..db27b41 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.cpp
+++ b/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.cpp
@@ -13,6 +13,8 @@
// limitations under the License.
#include "RenderChannelImpl.h"
+#include "android/base/synchronization/Lock.h"
+
#include <algorithm>
#include <utility>
@@ -21,147 +23,130 @@
namespace emugl {
-RenderChannelImpl::RenderChannelImpl(std::shared_ptr<RendererImpl> renderer)
- : mRenderer(std::move(renderer)), mState(State::Empty) {
- assert(mState.is_lock_free()); // rethink it if this fails - we've already
- // got a lock, use it instead.
+#define EMUGL_DEBUG_LEVEL 0
+#include "emugl/common/debug.h"
+
+using Buffer = RenderChannel::Buffer;
+using IoResult = RenderChannel::IoResult;
+using State = RenderChannel::State;
+using AutoLock = android::base::AutoLock;
+
+// These constants correspond to the capacities of buffer queues
+// used by each RenderChannelImpl instance. Benchmarking shows that
+// it's important to have a large queue for guest -> host transfers,
+// but a much smaller one works for host -> guest ones.
+static constexpr size_t kGuestToHostQueueCapacity = 1024U;
+static constexpr size_t kHostToGuestQueueCapacity = 16U;
+
+RenderChannelImpl::RenderChannelImpl()
+ : mLock(),
+ mFromGuest(kGuestToHostQueueCapacity, mLock),
+ mToGuest(kHostToGuestQueueCapacity, mLock) {
+ updateStateLocked();
}
-void RenderChannelImpl::setEventCallback(
- RenderChannelImpl::EventCallback callback) {
- mOnEvent = std::move(callback);
-
- // Reset the current state to make sure the new subscriber gets it.
- mState.store(State::Empty, std::memory_order_release);
- onEvent(true);
+void RenderChannelImpl::setEventCallback(EventCallback&& callback) {
+ mEventCallback = std::move(callback);
}
-bool RenderChannelImpl::write(ChannelBuffer&& buffer) {
- const bool res = mFromGuest.send(std::move(buffer));
- onEvent(true);
- return res;
+void RenderChannelImpl::setWantedEvents(State state) {
+ D("state=%d", (int)state);
+ AutoLock lock(mLock);
+ mWantedEvents |= state;
+ notifyStateChangeLocked();
}
-bool RenderChannelImpl::read(ChannelBuffer* buffer, CallType type) {
- if (type == CallType::Nonblocking && mToGuest.size() == 0) {
- return false;
- }
- const bool res = mToGuest.receive(buffer);
- onEvent(true);
- return res;
+RenderChannel::State RenderChannelImpl::state() const {
+ AutoLock lock(mLock);
+ return mState;
+}
+
+IoResult RenderChannelImpl::tryWrite(Buffer&& buffer) {
+ D("buffer size=%d", (int)buffer.size());
+ AutoLock lock(mLock);
+ auto result = mFromGuest.tryPushLocked(std::move(buffer));
+ updateStateLocked();
+ DD("mFromGuest.tryPushLocked() returned %d, state %d", (int)result,
+ (int)mState);
+ return result;
+}
+
+IoResult RenderChannelImpl::tryRead(Buffer* buffer) {
+ D("enter");
+ AutoLock lock(mLock);
+ auto result = mToGuest.tryPopLocked(buffer);
+ updateStateLocked();
+ DD("mToGuest.tryPopLocked() returned %d, buffer size %d, state %d",
+ (int)result, (int)buffer->size(), (int)mState);
+ return result;
}
void RenderChannelImpl::stop() {
- stop(true);
+ D("enter");
+ AutoLock lock(mLock);
+ mFromGuest.closeLocked();
+ mToGuest.closeLocked();
}
-void RenderChannelImpl::forceStop() {
- stop(false);
+bool RenderChannelImpl::writeToGuest(Buffer&& buffer) {
+ D("buffer size=%d", (int)buffer.size());
+ AutoLock lock(mLock);
+ IoResult result = mToGuest.pushLocked(std::move(buffer));
+ updateStateLocked();
+ DD("mToGuest.pushLocked() returned %d, state %d", (int)result, (int)mState);
+ notifyStateChangeLocked();
+ return result == IoResult::Ok;
}
-void RenderChannelImpl::stop(bool byGuest) {
- if (mStopped) {
- return;
- }
- mStopped = true;
- mFromGuest.stop();
- mToGuest.stop();
- onEvent(byGuest);
-}
-
-bool RenderChannelImpl::isStopped() const {
- return mStopped;
-}
-
-void RenderChannelImpl::writeToGuest(ChannelBuffer&& buf) {
- mToGuest.send(std::move(buf));
- onEvent(false);
-}
-
-size_t RenderChannelImpl::readFromGuest(ChannelBuffer::value_type* buf,
- size_t size,
- bool blocking) {
- assert(buf);
-
- size_t read = 0;
- const auto bufEnd = buf + size;
- while (buf != bufEnd && !mStopped) {
- if (mFromGuestBufferLeft == 0) {
- if (mFromGuest.size() == 0 && (read > 0 || !blocking)) {
- break;
- }
- if (!mFromGuest.receive(&mFromGuestBuffer)) {
- break;
- }
- mFromGuestBufferLeft = mFromGuestBuffer.size();
- }
-
- const size_t curSize =
- std::min<size_t>(bufEnd - buf, mFromGuestBufferLeft);
- memcpy(buf, mFromGuestBuffer.data() +
- (mFromGuestBuffer.size() - mFromGuestBufferLeft),
- curSize);
-
- read += curSize;
- buf += curSize;
- mFromGuestBufferLeft -= curSize;
- }
- onEvent(false);
- return read;
-}
-
-void RenderChannelImpl::onEvent(bool byGuest) {
- if (!mOnEvent) {
- return;
- }
-
- // We need to make sure that the state returned from calcState() remains
- // valid when we write it into mState; this means we need to lock both
- // calcState() and mState assignment with the same lock - otherwise it is
- // possible (and it happened before the lock was added) that some other
- // thread would overwrite a newer state with its older value, e.g.:
- // - thread 1 reads the last message from |mToGuest|
- // - thread 1 enters onEvent()
- // - thread 1 calculates state 1 = "can't read", switches out
- // - thread 2 writes some data into |mToGuest|
- // - thread 2 enters onEvent()
- // - thread 2 calculates state 2 = "can read"
- // - thread 2 gets the lock
- // - thread 2 updates |mState| to "can read", unlocks, switches out
- // - thread 1 gets the lock
- // - thread 1 overwrites |mState| with state 1 = "can't read"
- // - thread 1 unlocks and calls the |mOnEvent| callback.
- //
- // The result is that state 2 = "can read" is completely lost - callback
- // is never called when |mState| is "can read".
- //
- // But if the whole block of code is locked, threads can't overwrite newer
- // |mState| with some older value, and the described situation would never
- // happen.
- android::base::AutoLock lock(mStateLock);
- const State newState = calcState();
- if (mState != newState) {
- mState = newState;
- lock.unlock();
-
- mOnEvent(newState,
- byGuest ? EventSource::Client : EventSource::RenderChannel);
- }
-}
-
-RenderChannel::State RenderChannelImpl::calcState() const {
- State state = State::Empty;
- if (mStopped) {
- state = State::Stopped;
+IoResult RenderChannelImpl::readFromGuest(Buffer* buffer, bool blocking) {
+ D("enter");
+ AutoLock lock(mLock);
+ IoResult result;
+ if (blocking) {
+ result = mFromGuest.popLocked(buffer);
} else {
- if (mFromGuest.size() < mFromGuest.capacity()) {
- state |= State::CanWrite;
- }
- if (mToGuest.size() > 0) {
- state |= State::CanRead;
- }
+ result = mFromGuest.tryPopLocked(buffer);
}
- return state;
+ updateStateLocked();
+ DD("mFromGuest.%s() return %d, buffer size %d, state %d",
+ blocking ? "popLocked" : "tryPopLocked", (int)result,
+ (int)buffer->size(), (int)mState);
+ notifyStateChangeLocked();
+ return result;
+}
+
+void RenderChannelImpl::stopFromHost() {
+ D("enter");
+
+ AutoLock lock(mLock);
+ mFromGuest.closeLocked();
+ mToGuest.closeLocked();
+ mState |= State::Stopped;
+ notifyStateChangeLocked();
+}
+
+void RenderChannelImpl::updateStateLocked() {
+ State state = RenderChannel::State::Empty;
+
+ if (mToGuest.canPopLocked()) {
+ state |= State::CanRead;
+ }
+ if (mFromGuest.canPushLocked()) {
+ state |= State::CanWrite;
+ }
+ if (mToGuest.isClosedLocked()) {
+ state |= State::Stopped;
+ }
+ mState = state;
+}
+
+void RenderChannelImpl::notifyStateChangeLocked() {
+ State available = mState & mWantedEvents;
+ if (available != 0) {
+ D("callback with %d", (int)available);
+ mWantedEvents &= ~mState;
+ mEventCallback(available);
+ }
}
} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.h b/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.h
index eca2966..0d306d1 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.h
+++ b/distrib/android-emugl/host/libs/libOpenglRender/RenderChannelImpl.h
@@ -14,80 +14,76 @@
#pragma once
#include "OpenglRender/RenderChannel.h"
+#include "BufferQueue.h"
#include "RendererImpl.h"
-#include "android/base/Compiler.h"
-#include "android/base/synchronization/Lock.h"
-#include "android/base/synchronization/MessageChannel.h"
-
-#include <atomic>
-#include <memory>
-
namespace emugl {
+// Implementation of the RenderChannel interface that connects a guest
+// client thread (really an AndroidPipe implementation) to a host
+// RenderThread instance.
class RenderChannelImpl final : public RenderChannel {
public:
- RenderChannelImpl(std::shared_ptr<RendererImpl> renderer);
+ // Default constructor.
+ RenderChannelImpl();
-public:
- // RenderChannel implementation, operations provided for a guest system
- virtual void setEventCallback(EventCallback callback) override final;
+ /////////////////////////////////////////////////////////////////
+ // RenderChannel overriden methods. These are called from the guest
+ // client thread.
- virtual bool write(ChannelBuffer&& buffer) override final;
- virtual bool read(ChannelBuffer* buffer, CallType type) override final;
+ // Set the event |callback| to be notified when the host changes the
+ // state of the channel, according to the event mask provided by
+ // setWantedEvents(). Call this function right after creating the
+ // instance.
+ virtual void setEventCallback(EventCallback&& callback) override final;
- virtual State currentState() const override final {
- return mState.load(std::memory_order_acquire);
- }
+ // Set the mask of events the guest wants to be notified of from the
+ // host thread.
+ virtual void setWantedEvents(State state) override final;
+ // Return the current channel state relative to the guest.
+ virtual State state() const override final;
+
+ // Try to send a buffer from the guest to the host render thread.
+ virtual IoResult tryWrite(Buffer&& buffer) override final;
+
+ // Try to read a buffer from the host render thread into the guest.
+ virtual IoResult tryRead(Buffer* buffer) override final;
+
+ // Close the channel from the guest.
virtual void stop() override final;
- virtual bool isStopped() const override final;
-public:
- // These functions are for the RenderThread, they could be called in
- // parallel with the ones from the RenderChannel interface. Make sure the
- // internal state remains consistent all the time.
- void writeToGuest(ChannelBuffer&& buf);
- size_t readFromGuest(ChannelBuffer::value_type* buf, size_t size,
- bool blocking);
- void forceStop();
+ /////////////////////////////////////////////////////////////////
+ // These functions are called from the host render thread.
+
+ // Send a buffer to the guest, this call is blocking. On success,
+ // move |buffer| into the channel and return true. On failure, return
+ // false (meaning that the channel was closed).
+ bool writeToGuest(Buffer&& buffer);
+
+ // Read data from the guest. If |blocking| is true, the call will be
+ // blocking. On success, move item into |*buffer| and return true. On
+ // failure, return IoResult::Error to indicate the channel was closed,
+ // or IoResult::TryAgain to indicate it was empty (this can happen only
+ // if |blocking| is false).
+ IoResult readFromGuest(Buffer* buffer, bool blocking);
+
+ // Close the channel from the host.
+ void stopFromHost();
private:
- DISALLOW_COPY_ASSIGN_AND_MOVE(RenderChannelImpl);
+ void updateStateLocked();
+ void notifyStateChangeLocked();
-private:
- void onEvent(bool byGuest);
- State calcState() const;
- void stop(bool byGuest);
+ EventCallback mEventCallback;
-private:
- std::shared_ptr<RendererImpl> mRenderer;
-
- EventCallback mOnEvent;
-
- // Protects the state recalculation and writes to mState.
- //
- // The correctness condition governing the relationship between mFromGuest,
- // mToGuest, and mState is that we can't reach a potentially stable state
- // (i.e., at the end of a set of invocations of publically-visible
- // operations) in which either:
- // - mFromGuest().size() < mFromGuest.capacity(), yet state does not have
- // State::CanWrite, or
- // - mToGuest().size > 0, yet state does not have State::CanRead.
- // Clients assume they can poll currentState() and have the indicate whether
- // a write or read might possibly succeed, and this correctness condition
- // makes that assumption valid -- if a write or read might succeed,
- // mState is required to eventually indicate this.
- android::base::Lock mStateLock;
- std::atomic<State> mState;
-
- bool mStopped = false;
-
- android::base::MessageChannel<ChannelBuffer, 1024> mFromGuest;
- android::base::MessageChannel<ChannelBuffer, 16> mToGuest;
-
- ChannelBuffer mFromGuestBuffer;
- size_t mFromGuestBufferLeft = 0;
+ // A single lock to protect the state and the two buffer queues at the
+ // same time. NOTE: This needs to appear before the BufferQueue instances.
+ mutable android::base::Lock mLock;
+ State mState = State::Empty;
+ State mWantedEvents = State::Empty;
+ BufferQueue mFromGuest;
+ BufferQueue mToGuest;
};
} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.cpp b/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.cpp
index bf7f45f..a34c985 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.cpp
+++ b/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.cpp
@@ -31,38 +31,43 @@
#include "android/base/system/System.h"
+#define EMUGL_DEBUG_LEVEL 0
+#include "emugl/common/debug.h"
+
#include <assert.h>
#include <stdio.h>
#include <string.h>
+namespace emugl {
+
// Start with a smaller buffer to not waste memory on a low-used render threads.
static constexpr int kStreamBufferSize = 128 * 1024;
-RenderThread::RenderThread(std::weak_ptr<emugl::RendererImpl> renderer,
- std::shared_ptr<emugl::RenderChannelImpl> channel)
+RenderThread::RenderThread(std::weak_ptr<RendererImpl> renderer,
+ std::shared_ptr<RenderChannelImpl> channel)
: mChannel(channel), mRenderer(renderer) {}
RenderThread::~RenderThread() = default;
// static
std::unique_ptr<RenderThread> RenderThread::create(
- std::weak_ptr<emugl::RendererImpl> renderer,
- std::shared_ptr<emugl::RenderChannelImpl> channel) {
+ std::weak_ptr<RendererImpl> renderer,
+ std::shared_ptr<RenderChannelImpl> channel) {
return std::unique_ptr<RenderThread>(
new RenderThread(renderer, channel));
}
intptr_t RenderThread::main() {
+ ChannelStream stream(mChannel, RenderChannel::Buffer::kSmallSize);
+
uint32_t flags = 0;
- if (mChannel->readFromGuest(reinterpret_cast<char*>(&flags),
- sizeof(flags), true) != sizeof(flags)) {
+ if (stream.read(&flags, sizeof(flags)) != sizeof(flags)) {
return 0;
}
// |flags| used to have something, now they're not used.
(void)flags;
- ChannelStream stream(mChannel, emugl::ChannelBuffer::kSmallSize);
RenderThreadInfo tInfo;
ChecksumCalculatorThreadInfo tChecksumInfo;
ChecksumCalculator& checksumCalc = tChecksumInfo.get();
@@ -114,8 +119,12 @@
const int stat = readBuf.getData(&stream, packetSize);
if (stat <= 0) {
+ D("Warning: render thread could not read data from stream");
break;
}
+ DD("render thread read %d bytes, op %d, packet size %d",
+ (int)readBuf.validData(), *(int32_t*)readBuf.buf(),
+ *(int32_t*)(readBuf.buf() + 4));
//
// log received bandwidth statistics
@@ -214,3 +223,5 @@
return 0;
}
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.h b/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.h
index 7b4b63e..723f274 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.h
+++ b/distrib/android-emugl/host/libs/libOpenglRender/RenderThread.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
#pragma once
-#include "IOStream.h"
#include "emugl/common/mutex.h"
#include "emugl/common/thread.h"
@@ -22,9 +21,9 @@
#include <memory>
namespace emugl {
+
class RenderChannelImpl;
class RendererImpl;
-}
// A class used to model a thread of the RenderServer. Each one of them
// handles a single guest client / protocol byte stream.
@@ -32,8 +31,8 @@
public:
// Create a new RenderThread instance.
static std::unique_ptr<RenderThread> create(
- std::weak_ptr<emugl::RendererImpl> renderer,
- std::shared_ptr<emugl::RenderChannelImpl> channel);
+ std::weak_ptr<RendererImpl> renderer,
+ std::shared_ptr<RenderChannelImpl> channel);
virtual ~RenderThread();
@@ -42,11 +41,13 @@
bool isFinished() { return tryWait(NULL); }
private:
- RenderThread(std::weak_ptr<emugl::RendererImpl> renderer,
- std::shared_ptr<emugl::RenderChannelImpl> channel);
+ RenderThread(std::weak_ptr<RendererImpl> renderer,
+ std::shared_ptr<RenderChannelImpl> channel);
virtual intptr_t main();
- std::shared_ptr<emugl::RenderChannelImpl> mChannel;
- std::weak_ptr<emugl::RendererImpl> mRenderer;
+ std::shared_ptr<RenderChannelImpl> mChannel;
+ std::weak_ptr<RendererImpl> mRenderer;
};
+
+} // namespace emugl
diff --git a/distrib/android-emugl/host/libs/libOpenglRender/RendererImpl.cpp b/distrib/android-emugl/host/libs/libOpenglRender/RendererImpl.cpp
index ff14aac..68d646f 100644
--- a/distrib/android-emugl/host/libs/libOpenglRender/RendererImpl.cpp
+++ b/distrib/android-emugl/host/libs/libOpenglRender/RendererImpl.cpp
@@ -92,7 +92,7 @@
for (const auto& t : threads) {
if (const auto channel = t.second.lock()) {
- channel->forceStop();
+ channel->stopFromHost();
}
}
// We're stopping the renderer, so there's no need to clean up resources
@@ -106,8 +106,7 @@
}
RenderChannelPtr RendererImpl::createRenderChannel() {
- const auto channel =
- std::make_shared<RenderChannelImpl>(shared_from_this());
+ const auto channel = std::make_shared<RenderChannelImpl>();
std::unique_ptr<RenderThread> rt(RenderThread::create(
shared_from_this(), channel));
diff --git a/distrib/android-emugl/host/tools/emugen/ApiGen.cpp b/distrib/android-emugl/host/tools/emugen/ApiGen.cpp
index 8af6e29..fc51ebc 100644
--- a/distrib/android-emugl/host/tools/emugen/ApiGen.cpp
+++ b/distrib/android-emugl/host/tools/emugen/ApiGen.cpp
@@ -803,7 +803,7 @@
fprintf(fp, "\n#ifndef GUARD_%s\n", classname.c_str());
fprintf(fp, "#define GUARD_%s\n\n", classname.c_str());
- fprintf(fp, "#include \"IOStream.h\"\n");
+ fprintf(fp, "#include \"OpenglRender/IOStream.h\"\n");
fprintf(fp, "#include \"ChecksumCalculator.h\"\n");
fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(SERVER_SIDE));
fprintf(fp, "#include \"emugl/common/logging.h\"\n");
diff --git a/distrib/android-emugl/host/tools/emugen/tests/t.001/expected/decoder/foo_dec.h b/distrib/android-emugl/host/tools/emugen/tests/t.001/expected/decoder/foo_dec.h
index 30ada6f..d025090 100644
--- a/distrib/android-emugl/host/tools/emugen/tests/t.001/expected/decoder/foo_dec.h
+++ b/distrib/android-emugl/host/tools/emugen/tests/t.001/expected/decoder/foo_dec.h
@@ -4,7 +4,7 @@
#ifndef GUARD_foo_decoder_context_t
#define GUARD_foo_decoder_context_t
-#include "IOStream.h"
+#include "OpenglRender/IOStream.h"
#include "ChecksumCalculator.h"
#include "foo_server_context.h"
diff --git a/distrib/android-emugl/shared/OpenglCodecCommon/Android.mk b/distrib/android-emugl/shared/OpenglCodecCommon/Android.mk
index 509812a..e5cefae 100644
--- a/distrib/android-emugl/shared/OpenglCodecCommon/Android.mk
+++ b/distrib/android-emugl/shared/OpenglCodecCommon/Android.mk
@@ -17,7 +17,7 @@
LOCAL_SRC_FILES := $(host_commonSources)
$(call emugl-import, libemugl_common)
-$(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/host/include/libOpenglRender $(LOCAL_PATH))
+$(call emugl-export,C_INCLUDES,$(EMUGL_PATH)/host/include/OpenglRender $(LOCAL_PATH))
$(call emugl-export,LDLIBS,$(host_commonLdLibs))
$(call emugl-end-module)
diff --git a/distrib/android-emugl/shared/emugl/common/debug.h b/distrib/android-emugl/shared/emugl/common/debug.h
new file mode 100644
index 0000000..d431381
--- /dev/null
+++ b/distrib/android-emugl/shared/emugl/common/debug.h
@@ -0,0 +1,33 @@
+/*
+* 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
+
+// Usage: Define EMUGL_DEBUG_LEVEL before including this header to
+// select the behaviour of the D() and DD() macros.
+
+#if defined(EMUGL_DEBUG_LEVEL) && EMUGL_DEBUG_LEVEL > 0
+#include <stdio.h>
+#define D(...) (printf("%s:%d:%s: ", __FILE__, __LINE__, __func__), printf(__VA_ARGS__), printf("\n"), fflush(stdout))
+#else
+#define D(...) (void)0
+#endif // EMUGL_DEBUG_LEVEL > 0
+
+#if defined(EMUGL_DEBUG_LEVEL) && EMUGL_DEBUG_LEVEL > 1
+#define DD(...) D(__VA_ARGS__)
+#else
+#define DD(...) (void)0
+#endif // EMUGL_DEBUG_LEVEL > 1