blob: 3a00a4bc748e7e74ec33b2cd53381f915ac27114 [file] [log] [blame]
// 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