diff options
Diffstat (limited to 'libs')
51 files changed, 6951 insertions, 4022 deletions
diff --git a/libs/binder/Android.mk b/libs/binder/Android.mk index 673fc82..d8ae0aa 100644 --- a/libs/binder/Android.mk +++ b/libs/binder/Android.mk @@ -21,6 +21,7 @@ sources := \ Debug.cpp \ IAppOpsCallback.cpp \ IAppOpsService.cpp \ + IBatteryStats.cpp \ IInterface.cpp \ IMemory.cpp \ IPCThreadState.cpp \ diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp new file mode 100644 index 0000000..6469b08 --- /dev/null +++ b/libs/binder/IBatteryStats.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 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. + */ + +#include <binder/IBatteryStats.h> + +#include <utils/Debug.h> +#include <utils/Log.h> +#include <binder/Parcel.h> +#include <utils/String8.h> + +#include <private/binder/Static.h> + +namespace android { + +// ---------------------------------------------------------------------- + +class BpBatteryStats : public BpInterface<IBatteryStats> +{ +public: + BpBatteryStats(const sp<IBinder>& impl) + : BpInterface<IBatteryStats>(impl) + { + } + + virtual void noteStartSensor(int uid, int sensor) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + data.writeInt32(sensor); + remote()->transact(NOTE_START_SENSOR_TRANSACTION, data, &reply); + } + + virtual void noteStopSensor(int uid, int sensor) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + data.writeInt32(sensor); + remote()->transact(NOTE_STOP_SENSOR_TRANSACTION, data, &reply); + } +}; + +IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats"); + +// ---------------------------------------------------------------------- + +status_t BnBatteryStats::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case NOTE_START_SENSOR_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + int sensor = data.readInt32(); + noteStartSensor(uid, sensor); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_STOP_SENSOR_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + int sensor = data.readInt32(); + noteStopSensor(uid, sensor); + reply->writeNoException(); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +}; // namespace android diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp index a14c100..8739625 100644 --- a/libs/binder/MemoryDealer.cpp +++ b/libs/binder/MemoryDealer.cpp @@ -225,8 +225,8 @@ Allocation::~Allocation() // ---------------------------------------------------------------------------- -MemoryDealer::MemoryDealer(size_t size, const char* name) - : mHeap(new MemoryHeapBase(size, 0, name)), +MemoryDealer::MemoryDealer(size_t size, const char* name, uint32_t flags) + : mHeap(new MemoryHeapBase(size, flags, name)), mAllocator(new SimpleBestFitAllocator(size)) { } diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index 67cb428..4298522 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -780,6 +780,32 @@ status_t Parcel::writeDupFileDescriptor(int fd) return err; } +// WARNING: This method must stay in sync with +// Parcelable.Creator<ParcelFileDescriptor> CREATOR +// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java +status_t Parcel::writeParcelFileDescriptor(int fd, int commChannel) { + status_t status; + + if (fd < 0) { + status = writeInt32(0); // ParcelFileDescriptor is null + if (status) return status; + } else { + status = writeInt32(1); // ParcelFileDescriptor is not null + if (status) return status; + status = writeDupFileDescriptor(fd); + if (status) return status; + if (commChannel < 0) { + status = writeInt32(0); // commChannel is null + if (status) return status; + } else { + status = writeInt32(1); // commChannel is not null + if (status) return status; + status = writeDupFileDescriptor(commChannel); + } + } + return status; +} + status_t Parcel::writeBlob(size_t len, WritableBlob* outBlob) { status_t status; @@ -1196,6 +1222,23 @@ int Parcel::readFileDescriptor() const return BAD_TYPE; } +// WARNING: This method must stay in sync with writeToParcel() +// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java +int Parcel::readParcelFileDescriptor(int& outCommChannel) const { + int fd; + outCommChannel = -1; + + if (readInt32() == 0) { + fd = -1; + } else { + fd = readFileDescriptor(); + if (fd >= 0 && readInt32() != 0) { + outCommChannel = readFileDescriptor(); + } + } + return fd; +} + status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const { int32_t useAshmem; diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk index c14c950..0a77317 100644 --- a/libs/gui/Android.mk +++ b/libs/gui/Android.mk @@ -5,8 +5,13 @@ LOCAL_SRC_FILES:= \ IGraphicBufferConsumer.cpp \ IConsumerListener.cpp \ BitTube.cpp \ + BufferItem.cpp \ BufferItemConsumer.cpp \ BufferQueue.cpp \ + BufferQueueConsumer.cpp \ + BufferQueueCore.cpp \ + BufferQueueProducer.cpp \ + BufferSlot.cpp \ ConsumerBase.cpp \ CpuConsumer.cpp \ DisplayEventReceiver.cpp \ diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp new file mode 100644 index 0000000..d3fa43e --- /dev/null +++ b/libs/gui/BufferItem.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2014 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 <gui/BufferItem.h> + +#include <ui/Fence.h> +#include <ui/GraphicBuffer.h> + +#include <system/window.h> + +namespace android { + +BufferItem::BufferItem() : + mTransform(0), + mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTimestamp(0), + mIsAutoTimestamp(false), + mFrameNumber(0), + mSlot(INVALID_BUFFER_SLOT), + mIsDroppable(false), + mAcquireCalled(false), + mTransformToDisplayInverse(false) { + mCrop.makeInvalid(); +} + +BufferItem::operator IGraphicBufferConsumer::BufferItem() const { + IGraphicBufferConsumer::BufferItem bufferItem; + bufferItem.mGraphicBuffer = mGraphicBuffer; + bufferItem.mFence = mFence; + bufferItem.mCrop = mCrop; + bufferItem.mTransform = mTransform; + bufferItem.mScalingMode = mScalingMode; + bufferItem.mTimestamp = mTimestamp; + bufferItem.mIsAutoTimestamp = mIsAutoTimestamp; + bufferItem.mFrameNumber = mFrameNumber; + bufferItem.mBuf = mSlot; + bufferItem.mIsDroppable = mIsDroppable; + bufferItem.mAcquireCalled = mAcquireCalled; + bufferItem.mTransformToDisplayInverse = mTransformToDisplayInverse; + return bufferItem; +} + +size_t BufferItem::getPodSize() const { + size_t c = sizeof(mCrop) + + sizeof(mTransform) + + sizeof(mScalingMode) + + sizeof(mTimestamp) + + sizeof(mIsAutoTimestamp) + + sizeof(mFrameNumber) + + sizeof(mSlot) + + sizeof(mIsDroppable) + + sizeof(mAcquireCalled) + + sizeof(mTransformToDisplayInverse); + return c; +} + +size_t BufferItem::getFlattenedSize() const { + size_t c = 0; + if (mGraphicBuffer != 0) { + c += mGraphicBuffer->getFlattenedSize(); + FlattenableUtils::align<4>(c); + } + if (mFence != 0) { + c += mFence->getFlattenedSize(); + FlattenableUtils::align<4>(c); + } + return sizeof(int32_t) + c + getPodSize(); +} + +size_t BufferItem::getFdCount() const { + size_t c = 0; + if (mGraphicBuffer != 0) { + c += mGraphicBuffer->getFdCount(); + } + if (mFence != 0) { + c += mFence->getFdCount(); + } + return c; +} + +status_t BufferItem::flatten( + void*& buffer, size_t& size, int*& fds, size_t& count) const { + + // make sure we have enough space + if (count < BufferItem::getFlattenedSize()) { + return NO_MEMORY; + } + + // content flags are stored first + uint32_t& flags = *static_cast<uint32_t*>(buffer); + + // advance the pointer + FlattenableUtils::advance(buffer, size, sizeof(uint32_t)); + + flags = 0; + if (mGraphicBuffer != 0) { + status_t err = mGraphicBuffer->flatten(buffer, size, fds, count); + if (err) return err; + size -= FlattenableUtils::align<4>(buffer); + flags |= 1; + } + if (mFence != 0) { + status_t err = mFence->flatten(buffer, size, fds, count); + if (err) return err; + size -= FlattenableUtils::align<4>(buffer); + flags |= 2; + } + + // check we have enough space (in case flattening the fence/graphicbuffer lied to us) + if (size < getPodSize()) { + return NO_MEMORY; + } + + FlattenableUtils::write(buffer, size, mCrop); + FlattenableUtils::write(buffer, size, mTransform); + FlattenableUtils::write(buffer, size, mScalingMode); + FlattenableUtils::write(buffer, size, mTimestamp); + FlattenableUtils::write(buffer, size, mIsAutoTimestamp); + FlattenableUtils::write(buffer, size, mFrameNumber); + FlattenableUtils::write(buffer, size, mSlot); + FlattenableUtils::write(buffer, size, mIsDroppable); + FlattenableUtils::write(buffer, size, mAcquireCalled); + FlattenableUtils::write(buffer, size, mTransformToDisplayInverse); + + return NO_ERROR; +} + +status_t BufferItem::unflatten( + void const*& buffer, size_t& size, int const*& fds, size_t& count) { + + if (size < sizeof(uint32_t)) + return NO_MEMORY; + + uint32_t flags = 0; + FlattenableUtils::read(buffer, size, flags); + + if (flags & 1) { + mGraphicBuffer = new GraphicBuffer(); + status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count); + if (err) return err; + size -= FlattenableUtils::align<4>(buffer); + } + + if (flags & 2) { + mFence = new Fence(); + status_t err = mFence->unflatten(buffer, size, fds, count); + if (err) return err; + size -= FlattenableUtils::align<4>(buffer); + } + + // check we have enough space + if (size < getPodSize()) { + return NO_MEMORY; + } + + FlattenableUtils::read(buffer, size, mCrop); + FlattenableUtils::read(buffer, size, mTransform); + FlattenableUtils::read(buffer, size, mScalingMode); + FlattenableUtils::read(buffer, size, mTimestamp); + FlattenableUtils::read(buffer, size, mIsAutoTimestamp); + FlattenableUtils::read(buffer, size, mFrameNumber); + FlattenableUtils::read(buffer, size, mSlot); + FlattenableUtils::read(buffer, size, mIsDroppable); + FlattenableUtils::read(buffer, size, mAcquireCalled); + FlattenableUtils::read(buffer, size, mTransformToDisplayInverse); + + return NO_ERROR; +} + +const char* BufferItem::scalingModeName(uint32_t scalingMode) { + switch (scalingMode) { + case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE"; + case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW"; + case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP"; + default: return "Unknown"; + } +} + +} // namespace android diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp index 350887a..fe50c55 100644 --- a/libs/gui/BufferItemConsumer.cpp +++ b/libs/gui/BufferItemConsumer.cpp @@ -29,12 +29,19 @@ namespace android { -BufferItemConsumer::BufferItemConsumer(const sp<BufferQueue>& bq, - uint32_t consumerUsage, int bufferCount, bool controlledByApp) : - ConsumerBase(bq, controlledByApp) +BufferItemConsumer::BufferItemConsumer( + const sp<IGraphicBufferConsumer>& consumer, uint32_t consumerUsage, + int bufferCount, bool controlledByApp) : + ConsumerBase(consumer, controlledByApp) { - mConsumer->setConsumerUsageBits(consumerUsage); - mConsumer->setMaxAcquiredBufferCount(bufferCount); + status_t err = mConsumer->setConsumerUsageBits(consumerUsage); + LOG_ALWAYS_FATAL_IF(err != OK, + "Failed to set consumer usage bits to %#x", consumerUsage); + if (bufferCount != DEFAULT_MAX_BUFFERS) { + err = mConsumer->setMaxAcquiredBufferCount(bufferCount); + LOG_ALWAYS_FATAL_IF(err != OK, + "Failed to set max acquired buffer count to %d", bufferCount); + } } BufferItemConsumer::~BufferItemConsumer() { diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp index 2aecb67..c306f9d 100644 --- a/libs/gui/BufferQueue.cpp +++ b/libs/gui/BufferQueue.cpp @@ -18,1225 +18,196 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS //#define LOG_NDEBUG 0 -#define GL_GLEXT_PROTOTYPES -#define EGL_EGLEXT_PROTOTYPES - -#include <EGL/egl.h> -#include <EGL/eglext.h> - #include <gui/BufferQueue.h> -#include <gui/IConsumerListener.h> -#include <gui/ISurfaceComposer.h> -#include <private/gui/ComposerService.h> - -#include <utils/Log.h> -#include <utils/Trace.h> -#include <utils/CallStack.h> - -// Macros for including the BufferQueue name in log messages -#define ST_LOGV(x, ...) ALOGV("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) -#define ST_LOGD(x, ...) ALOGD("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) -#define ST_LOGI(x, ...) ALOGI("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) -#define ST_LOGW(x, ...) ALOGW("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) -#define ST_LOGE(x, ...) ALOGE("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) - -#define ATRACE_BUFFER_INDEX(index) \ - if (ATRACE_ENABLED()) { \ - char ___traceBuf[1024]; \ - snprintf(___traceBuf, 1024, "%s: %d", mConsumerName.string(), \ - (index)); \ - android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \ - } +#include <gui/BufferQueueCore.h> namespace android { -// Get an ID that's unique within this process. -static int32_t createProcessUniqueId() { - static volatile int32_t globalCounter = 0; - return android_atomic_inc(&globalCounter); -} +BufferQueue::ProxyConsumerListener::ProxyConsumerListener( + const wp<ConsumerListener>& consumerListener): + mConsumerListener(consumerListener) {} + +BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {} -static const char* scalingModeName(int scalingMode) { - switch (scalingMode) { - case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE"; - case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW"; - case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP"; - default: return "Unknown"; +void BufferQueue::ProxyConsumerListener::onFrameAvailable() { + sp<ConsumerListener> listener(mConsumerListener.promote()); + if (listener != NULL) { + listener->onFrameAvailable(); } } -BufferQueue::BufferQueue(const sp<IGraphicBufferAlloc>& allocator) : - mDefaultWidth(1), - mDefaultHeight(1), - mMaxAcquiredBufferCount(1), - mDefaultMaxBufferCount(2), - mOverrideMaxBufferCount(0), - mConsumerControlledByApp(false), - mDequeueBufferCannotBlock(false), - mUseAsyncBuffer(true), - mConnectedApi(NO_CONNECTED_API), - mAbandoned(false), - mFrameCounter(0), - mBufferHasBeenQueued(false), - mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888), - mConsumerUsageBits(0), - mTransformHint(0) -{ - // Choose a name using the PID and a process-unique ID. - mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId()); - - ST_LOGV("BufferQueue"); - if (allocator == NULL) { - sp<ISurfaceComposer> composer(ComposerService::getComposerService()); - mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); - if (mGraphicBufferAlloc == 0) { - ST_LOGE("createGraphicBufferAlloc() failed in BufferQueue()"); - } - } else { - mGraphicBufferAlloc = allocator; +void BufferQueue::ProxyConsumerListener::onBuffersReleased() { + sp<ConsumerListener> listener(mConsumerListener.promote()); + if (listener != NULL) { + listener->onBuffersReleased(); } } -BufferQueue::~BufferQueue() { - ST_LOGV("~BufferQueue"); -} +void BufferQueue::createBufferQueue(sp<BnGraphicBufferProducer>* outProducer, + sp<BnGraphicBufferConsumer>* outConsumer, + const sp<IGraphicBufferAlloc>& allocator) { + LOG_ALWAYS_FATAL_IF(outProducer == NULL, + "BufferQueue: outProducer must not be NULL"); + LOG_ALWAYS_FATAL_IF(outConsumer == NULL, + "BufferQueue: outConsumer must not be NULL"); -status_t BufferQueue::setDefaultMaxBufferCountLocked(int count) { - const int minBufferCount = mUseAsyncBuffer ? 2 : 1; - if (count < minBufferCount || count > NUM_BUFFER_SLOTS) - return BAD_VALUE; + sp<BufferQueueCore> core(new BufferQueueCore(allocator)); + *outProducer = new BufferQueueProducer(core); + *outConsumer = new BufferQueueConsumer(core); +} - mDefaultMaxBufferCount = count; - mDequeueCondition.broadcast(); +void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, + sp<IGraphicBufferConsumer>* outConsumer, + const sp<IGraphicBufferAlloc>& allocator) { + LOG_ALWAYS_FATAL_IF(outProducer == NULL, + "BufferQueue: outProducer must not be NULL"); + LOG_ALWAYS_FATAL_IF(outConsumer == NULL, + "BufferQueue: outConsumer must not be NULL"); - return NO_ERROR; + sp<BufferQueueCore> core(new BufferQueueCore(allocator)); + *outProducer = new BufferQueueProducer(core); + *outConsumer = new BufferQueueConsumer(core); } -void BufferQueue::setConsumerName(const String8& name) { - Mutex::Autolock lock(mMutex); - mConsumerName = name; +BufferQueue::BufferQueue(const sp<IGraphicBufferAlloc>& allocator) : + mProducer(), + mConsumer() +{ + sp<BufferQueueCore> core(new BufferQueueCore(allocator)); + mProducer = new BufferQueueProducer(core); + mConsumer = new BufferQueueConsumer(core); } -status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) { - Mutex::Autolock lock(mMutex); - mDefaultBufferFormat = defaultFormat; - return NO_ERROR; -} +BufferQueue::~BufferQueue() {} -status_t BufferQueue::setConsumerUsageBits(uint32_t usage) { - Mutex::Autolock lock(mMutex); - mConsumerUsageBits = usage; - return NO_ERROR; +void BufferQueue::binderDied(const wp<IBinder>& who) { + mProducer->binderDied(who); } -status_t BufferQueue::setTransformHint(uint32_t hint) { - ST_LOGV("setTransformHint: %02x", hint); - Mutex::Autolock lock(mMutex); - mTransformHint = hint; - return NO_ERROR; +int BufferQueue::query(int what, int* outValue) { + return mProducer->query(what, outValue); } status_t BufferQueue::setBufferCount(int bufferCount) { - ST_LOGV("setBufferCount: count=%d", bufferCount); - - sp<IConsumerListener> listener; - { - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("setBufferCount: BufferQueue has been abandoned!"); - return NO_INIT; - } - if (bufferCount > NUM_BUFFER_SLOTS) { - ST_LOGE("setBufferCount: bufferCount too large (max %d)", - NUM_BUFFER_SLOTS); - return BAD_VALUE; - } - - // Error out if the user has dequeued buffers - for (int i=0 ; i<NUM_BUFFER_SLOTS; i++) { - if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) { - ST_LOGE("setBufferCount: client owns some buffers"); - return -EINVAL; - } - } - - if (bufferCount == 0) { - mOverrideMaxBufferCount = 0; - mDequeueCondition.broadcast(); - return NO_ERROR; - } - - // fine to assume async to false before we're setting the buffer count - const int minBufferSlots = getMinMaxBufferCountLocked(false); - if (bufferCount < minBufferSlots) { - ST_LOGE("setBufferCount: requested buffer count (%d) is less than " - "minimum (%d)", bufferCount, minBufferSlots); - return BAD_VALUE; - } - - // here we're guaranteed that the client doesn't have dequeued buffers - // and will release all of its buffer references. We don't clear the - // queue, however, so currently queued buffers still get displayed. - freeAllBuffersLocked(); - mOverrideMaxBufferCount = bufferCount; - mDequeueCondition.broadcast(); - listener = mConsumerListener; - } // scope for lock - - if (listener != NULL) { - listener->onBuffersReleased(); - } - - return NO_ERROR; -} - -int BufferQueue::query(int what, int* outValue) -{ - ATRACE_CALL(); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("query: BufferQueue has been abandoned!"); - return NO_INIT; - } - - int value; - switch (what) { - case NATIVE_WINDOW_WIDTH: - value = mDefaultWidth; - break; - case NATIVE_WINDOW_HEIGHT: - value = mDefaultHeight; - break; - case NATIVE_WINDOW_FORMAT: - value = mDefaultBufferFormat; - break; - case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: - value = getMinUndequeuedBufferCount(false); - break; - case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: - value = (mQueue.size() >= 2); - break; - case NATIVE_WINDOW_CONSUMER_USAGE_BITS: - value = mConsumerUsageBits; - break; - default: - return BAD_VALUE; - } - outValue[0] = value; - return NO_ERROR; + return mProducer->setBufferCount(bufferCount); } status_t BufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) { - ATRACE_CALL(); - ST_LOGV("requestBuffer: slot=%d", slot); - Mutex::Autolock lock(mMutex); - if (mAbandoned) { - ST_LOGE("requestBuffer: BufferQueue has been abandoned!"); - return NO_INIT; - } - if (slot < 0 || slot >= NUM_BUFFER_SLOTS) { - ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d", - NUM_BUFFER_SLOTS, slot); - return BAD_VALUE; - } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { - ST_LOGE("requestBuffer: slot %d is not owned by the client (state=%d)", - slot, mSlots[slot].mBufferState); - return BAD_VALUE; - } - mSlots[slot].mRequestBufferCalled = true; - *buf = mSlots[slot].mGraphicBuffer; - return NO_ERROR; + return mProducer->requestBuffer(slot, buf); } status_t BufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence, bool async, uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { - ATRACE_CALL(); - ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage); - - if ((w && !h) || (!w && h)) { - ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h); - return BAD_VALUE; - } - - status_t returnFlags(OK); - EGLDisplay dpy = EGL_NO_DISPLAY; - EGLSyncKHR eglFence = EGL_NO_SYNC_KHR; - - { // Scope for the lock - Mutex::Autolock lock(mMutex); - - if (format == 0) { - format = mDefaultBufferFormat; - } - // turn on usage bits the consumer requested - usage |= mConsumerUsageBits; - - int found = -1; - bool tryAgain = true; - while (tryAgain) { - if (mAbandoned) { - ST_LOGE("dequeueBuffer: BufferQueue has been abandoned!"); - return NO_INIT; - } - - const int maxBufferCount = getMaxBufferCountLocked(async); - if (async && mOverrideMaxBufferCount) { - // FIXME: some drivers are manually setting the buffer-count (which they - // shouldn't), so we do this extra test here to handle that case. - // This is TEMPORARY, until we get this fixed. - if (mOverrideMaxBufferCount < maxBufferCount) { - ST_LOGE("dequeueBuffer: async mode is invalid with buffercount override"); - return BAD_VALUE; - } - } - - // Free up any buffers that are in slots beyond the max buffer - // count. - for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) { - assert(mSlots[i].mBufferState == BufferSlot::FREE); - if (mSlots[i].mGraphicBuffer != NULL) { - freeBufferLocked(i); - returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS; - } - } - - // look for a free buffer to give to the client - found = INVALID_BUFFER_SLOT; - int dequeuedCount = 0; - int acquiredCount = 0; - for (int i = 0; i < maxBufferCount; i++) { - const int state = mSlots[i].mBufferState; - switch (state) { - case BufferSlot::DEQUEUED: - dequeuedCount++; - break; - case BufferSlot::ACQUIRED: - acquiredCount++; - break; - case BufferSlot::FREE: - /* We return the oldest of the free buffers to avoid - * stalling the producer if possible. This is because - * the consumer may still have pending reads of the - * buffers in flight. - */ - if ((found < 0) || - mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) { - found = i; - } - break; - } - } - - // clients are not allowed to dequeue more than one buffer - // if they didn't set a buffer count. - if (!mOverrideMaxBufferCount && dequeuedCount) { - ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without " - "setting the buffer count"); - return -EINVAL; - } - - // See whether a buffer has been queued since the last - // setBufferCount so we know whether to perform the min undequeued - // buffers check below. - if (mBufferHasBeenQueued) { - // make sure the client is not trying to dequeue more buffers - // than allowed. - const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1); - const int minUndequeuedCount = getMinUndequeuedBufferCount(async); - if (newUndequeuedCount < minUndequeuedCount) { - ST_LOGE("dequeueBuffer: min undequeued buffer count (%d) " - "exceeded (dequeued=%d undequeudCount=%d)", - minUndequeuedCount, dequeuedCount, - newUndequeuedCount); - return -EBUSY; - } - } - - // If no buffer is found, wait for a buffer to be released or for - // the max buffer count to change. - tryAgain = found == INVALID_BUFFER_SLOT; - if (tryAgain) { - // return an error if we're in "cannot block" mode (producer and consumer - // are controlled by the application) -- however, the consumer is allowed - // to acquire briefly an extra buffer (which could cause us to have to wait here) - // and that's okay because we know the wait will be brief (it happens - // if we dequeue a buffer while the consumer has acquired one but not released - // the old one yet -- for e.g.: see GLConsumer::updateTexImage()). - if (mDequeueBufferCannotBlock && (acquiredCount <= mMaxAcquiredBufferCount)) { - ST_LOGE("dequeueBuffer: would block! returning an error instead."); - return WOULD_BLOCK; - } - mDequeueCondition.wait(mMutex); - } - } - - - if (found == INVALID_BUFFER_SLOT) { - // This should not happen. - ST_LOGE("dequeueBuffer: no available buffer slots"); - return -EBUSY; - } - - const int buf = found; - *outBuf = found; - - ATRACE_BUFFER_INDEX(buf); - - const bool useDefaultSize = !w && !h; - if (useDefaultSize) { - // use the default size - w = mDefaultWidth; - h = mDefaultHeight; - } - - mSlots[buf].mBufferState = BufferSlot::DEQUEUED; - - const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer); - if ((buffer == NULL) || - (uint32_t(buffer->width) != w) || - (uint32_t(buffer->height) != h) || - (uint32_t(buffer->format) != format) || - ((uint32_t(buffer->usage) & usage) != usage)) - { - mSlots[buf].mAcquireCalled = false; - mSlots[buf].mGraphicBuffer = NULL; - mSlots[buf].mRequestBufferCalled = false; - mSlots[buf].mEglFence = EGL_NO_SYNC_KHR; - mSlots[buf].mFence = Fence::NO_FENCE; - mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; - - returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION; - } - - - if (CC_UNLIKELY(mSlots[buf].mFence == NULL)) { - ST_LOGE("dequeueBuffer: about to return a NULL fence from mSlot. " - "buf=%d, w=%d, h=%d, format=%d", - buf, buffer->width, buffer->height, buffer->format); - } - - dpy = mSlots[buf].mEglDisplay; - eglFence = mSlots[buf].mEglFence; - *outFence = mSlots[buf].mFence; - mSlots[buf].mEglFence = EGL_NO_SYNC_KHR; - mSlots[buf].mFence = Fence::NO_FENCE; - } // end lock scope - - if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) { - status_t error; - sp<GraphicBuffer> graphicBuffer( - mGraphicBufferAlloc->createGraphicBuffer(w, h, format, usage, &error)); - if (graphicBuffer == 0) { - ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer failed"); - return error; - } - - { // Scope for the lock - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("dequeueBuffer: BufferQueue has been abandoned!"); - return NO_INIT; - } - - mSlots[*outBuf].mFrameNumber = ~0; - mSlots[*outBuf].mGraphicBuffer = graphicBuffer; - } - } - - if (eglFence != EGL_NO_SYNC_KHR) { - EGLint result = eglClientWaitSyncKHR(dpy, eglFence, 0, 1000000000); - // If something goes wrong, log the error, but return the buffer without - // synchronizing access to it. It's too late at this point to abort the - // dequeue operation. - if (result == EGL_FALSE) { - ST_LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError()); - } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { - ST_LOGE("dequeueBuffer: timeout waiting for fence"); - } - eglDestroySyncKHR(dpy, eglFence); - } + return mProducer->dequeueBuffer(outBuf, outFence, async, w, h, format, usage); +} - ST_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf, - mSlots[*outBuf].mFrameNumber, - mSlots[*outBuf].mGraphicBuffer->handle, returnFlags); +status_t BufferQueue::detachProducerBuffer(int slot) { + return mProducer->detachBuffer(slot); +} - return returnFlags; +status_t BufferQueue::attachProducerBuffer(int* slot, + const sp<GraphicBuffer>& buffer) { + return mProducer->attachBuffer(slot, buffer); } status_t BufferQueue::queueBuffer(int buf, const QueueBufferInput& input, QueueBufferOutput* output) { - ATRACE_CALL(); - ATRACE_BUFFER_INDEX(buf); - - Rect crop; - uint32_t transform; - int scalingMode; - int64_t timestamp; - bool isAutoTimestamp; - bool async; - sp<Fence> fence; - - input.deflate(×tamp, &isAutoTimestamp, &crop, &scalingMode, &transform, - &async, &fence); - - if (fence == NULL) { - ST_LOGE("queueBuffer: fence is NULL"); - return BAD_VALUE; - } - - switch (scalingMode) { - case NATIVE_WINDOW_SCALING_MODE_FREEZE: - case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: - case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: - case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP: - break; - default: - ST_LOGE("unknown scaling mode: %d", scalingMode); - return -EINVAL; - } - - sp<IConsumerListener> listener; - - { // scope for the lock - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("queueBuffer: BufferQueue has been abandoned!"); - return NO_INIT; - } - - const int maxBufferCount = getMaxBufferCountLocked(async); - if (async && mOverrideMaxBufferCount) { - // FIXME: some drivers are manually setting the buffer-count (which they - // shouldn't), so we do this extra test here to handle that case. - // This is TEMPORARY, until we get this fixed. - if (mOverrideMaxBufferCount < maxBufferCount) { - ST_LOGE("queueBuffer: async mode is invalid with buffercount override"); - return BAD_VALUE; - } - } - if (buf < 0 || buf >= maxBufferCount) { - ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d", - maxBufferCount, buf); - return -EINVAL; - } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { - ST_LOGE("queueBuffer: slot %d is not owned by the client " - "(state=%d)", buf, mSlots[buf].mBufferState); - return -EINVAL; - } else if (!mSlots[buf].mRequestBufferCalled) { - ST_LOGE("queueBuffer: slot %d was enqueued without requesting a " - "buffer", buf); - return -EINVAL; - } - - ST_LOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] " - "tr=%#x scale=%s", - buf, mFrameCounter + 1, timestamp, - crop.left, crop.top, crop.right, crop.bottom, - transform, scalingModeName(scalingMode)); - - const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer); - Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight()); - Rect croppedCrop; - crop.intersect(bufferRect, &croppedCrop); - if (croppedCrop != crop) { - ST_LOGE("queueBuffer: crop rect is not contained within the " - "buffer in slot %d", buf); - return -EINVAL; - } - - mSlots[buf].mFence = fence; - mSlots[buf].mBufferState = BufferSlot::QUEUED; - mFrameCounter++; - mSlots[buf].mFrameNumber = mFrameCounter; - - BufferItem item; - item.mAcquireCalled = mSlots[buf].mAcquireCalled; - item.mGraphicBuffer = mSlots[buf].mGraphicBuffer; - item.mCrop = crop; - item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; - item.mTransformToDisplayInverse = bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY); - item.mScalingMode = scalingMode; - item.mTimestamp = timestamp; - item.mIsAutoTimestamp = isAutoTimestamp; - item.mFrameNumber = mFrameCounter; - item.mBuf = buf; - item.mFence = fence; - item.mIsDroppable = mDequeueBufferCannotBlock || async; - - if (mQueue.empty()) { - // when the queue is empty, we can ignore "mDequeueBufferCannotBlock", and - // simply queue this buffer. - mQueue.push_back(item); - listener = mConsumerListener; - } else { - // when the queue is not empty, we need to look at the front buffer - // state and see if we need to replace it. - Fifo::iterator front(mQueue.begin()); - if (front->mIsDroppable) { - // buffer slot currently queued is marked free if still tracked - if (stillTracking(front)) { - mSlots[front->mBuf].mBufferState = BufferSlot::FREE; - // reset the frame number of the freed buffer so that it is the first in - // line to be dequeued again. - mSlots[front->mBuf].mFrameNumber = 0; - } - // and we record the new buffer in the queued list - *front = item; - } else { - mQueue.push_back(item); - listener = mConsumerListener; - } - } - - mBufferHasBeenQueued = true; - mDequeueCondition.broadcast(); - - output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, - mQueue.size()); - - ATRACE_INT(mConsumerName.string(), mQueue.size()); - } // scope for the lock - - // call back without lock held - if (listener != 0) { - listener->onFrameAvailable(); - } - return NO_ERROR; + return mProducer->queueBuffer(buf, input, output); } void BufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) { - ATRACE_CALL(); - ST_LOGV("cancelBuffer: slot=%d", buf); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGW("cancelBuffer: BufferQueue has been abandoned!"); - return; - } - - if (buf < 0 || buf >= NUM_BUFFER_SLOTS) { - ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d", - NUM_BUFFER_SLOTS, buf); - return; - } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { - ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", - buf, mSlots[buf].mBufferState); - return; - } else if (fence == NULL) { - ST_LOGE("cancelBuffer: fence is NULL"); - return; - } - mSlots[buf].mBufferState = BufferSlot::FREE; - mSlots[buf].mFrameNumber = 0; - mSlots[buf].mFence = fence; - mDequeueCondition.broadcast(); + mProducer->cancelBuffer(buf, fence); } - status_t BufferQueue::connect(const sp<IBinder>& token, int api, bool producerControlledByApp, QueueBufferOutput* output) { - ATRACE_CALL(); - ST_LOGV("connect: api=%d producerControlledByApp=%s", api, - producerControlledByApp ? "true" : "false"); - Mutex::Autolock lock(mMutex); - -retry: - if (mAbandoned) { - ST_LOGE("connect: BufferQueue has been abandoned!"); - return NO_INIT; - } - - if (mConsumerListener == NULL) { - ST_LOGE("connect: BufferQueue has no consumer!"); - return NO_INIT; - } - - if (mConnectedApi != NO_CONNECTED_API) { - ST_LOGE("connect: already connected (cur=%d, req=%d)", - mConnectedApi, api); - return -EINVAL; - } - - // If we disconnect and reconnect quickly, we can be in a state where our slots are - // empty but we have many buffers in the queue. This can cause us to run out of - // memory if we outrun the consumer. Wait here if it looks like we have too many - // buffers queued up. - int maxBufferCount = getMaxBufferCountLocked(false); // worst-case, i.e. largest value - if (mQueue.size() > (size_t) maxBufferCount) { - // TODO: make this bound tighter? - ST_LOGV("queue size is %d, waiting", mQueue.size()); - mDequeueCondition.wait(mMutex); - goto retry; - } - - int err = NO_ERROR; - switch (api) { - case NATIVE_WINDOW_API_EGL: - case NATIVE_WINDOW_API_CPU: - case NATIVE_WINDOW_API_MEDIA: - case NATIVE_WINDOW_API_CAMERA: - mConnectedApi = api; - output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, mQueue.size()); - - // set-up a death notification so that we can disconnect - // automatically when/if the remote producer dies. - if (token != NULL && token->remoteBinder() != NULL) { - status_t err = token->linkToDeath(static_cast<IBinder::DeathRecipient*>(this)); - if (err == NO_ERROR) { - mConnectedProducerToken = token; - } else { - ALOGE("linkToDeath failed: %s (%d)", strerror(-err), err); - } - } - break; - default: - err = -EINVAL; - break; - } - - mBufferHasBeenQueued = false; - mDequeueBufferCannotBlock = mConsumerControlledByApp && producerControlledByApp; - - return err; -} - -void BufferQueue::binderDied(const wp<IBinder>& who) { - // If we're here, it means that a producer we were connected to died. - // We're GUARANTEED that we still are connected to it because it has no other way - // to get disconnected -- or -- we wouldn't be here because we're removing this - // callback upon disconnect. Therefore, it's okay to read mConnectedApi without - // synchronization here. - int api = mConnectedApi; - this->disconnect(api); + return mProducer->connect(token, api, producerControlledByApp, output); } status_t BufferQueue::disconnect(int api) { - ATRACE_CALL(); - ST_LOGV("disconnect: api=%d", api); - - int err = NO_ERROR; - sp<IConsumerListener> listener; - - { // Scope for the lock - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - // it is not really an error to disconnect after the surface - // has been abandoned, it should just be a no-op. - return NO_ERROR; - } - - switch (api) { - case NATIVE_WINDOW_API_EGL: - case NATIVE_WINDOW_API_CPU: - case NATIVE_WINDOW_API_MEDIA: - case NATIVE_WINDOW_API_CAMERA: - if (mConnectedApi == api) { - freeAllBuffersLocked(); - // remove our death notification callback if we have one - sp<IBinder> token = mConnectedProducerToken; - if (token != NULL) { - // this can fail if we're here because of the death notification - // either way, we just ignore. - token->unlinkToDeath(static_cast<IBinder::DeathRecipient*>(this)); - } - mConnectedProducerToken = NULL; - mConnectedApi = NO_CONNECTED_API; - mDequeueCondition.broadcast(); - listener = mConsumerListener; - } else { - ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)", - mConnectedApi, api); - err = -EINVAL; - } - break; - default: - ST_LOGE("disconnect: unknown API %d", api); - err = -EINVAL; - break; - } - } - - if (listener != NULL) { - listener->onBuffersReleased(); - } - - return err; + return mProducer->disconnect(api); } -void BufferQueue::dump(String8& result, const char* prefix) const { - Mutex::Autolock _l(mMutex); - - String8 fifo; - int fifoSize = 0; - Fifo::const_iterator i(mQueue.begin()); - while (i != mQueue.end()) { - fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], " - "xform=0x%02x, time=%#llx, scale=%s\n", - i->mBuf, i->mGraphicBuffer.get(), - i->mCrop.left, i->mCrop.top, i->mCrop.right, - i->mCrop.bottom, i->mTransform, i->mTimestamp, - scalingModeName(i->mScalingMode) - ); - i++; - fifoSize++; - } - - - result.appendFormat( - "%s-BufferQueue mMaxAcquiredBufferCount=%d, mDequeueBufferCannotBlock=%d, default-size=[%dx%d], " - "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n", - prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock, mDefaultWidth, - mDefaultHeight, mDefaultBufferFormat, mTransformHint, - fifoSize, fifo.string()); - - struct { - const char * operator()(int state) const { - switch (state) { - case BufferSlot::DEQUEUED: return "DEQUEUED"; - case BufferSlot::QUEUED: return "QUEUED"; - case BufferSlot::FREE: return "FREE"; - case BufferSlot::ACQUIRED: return "ACQUIRED"; - default: return "Unknown"; - } - } - } stateName; - - // just trim the free buffers to not spam the dump - int maxBufferCount = 0; - for (int i=NUM_BUFFER_SLOTS-1 ; i>=0 ; i--) { - const BufferSlot& slot(mSlots[i]); - if ((slot.mBufferState != BufferSlot::FREE) || (slot.mGraphicBuffer != NULL)) { - maxBufferCount = i+1; - break; - } - } - - for (int i=0 ; i<maxBufferCount ; i++) { - const BufferSlot& slot(mSlots[i]); - const sp<GraphicBuffer>& buf(slot.mGraphicBuffer); - result.appendFormat( - "%s%s[%02d:%p] state=%-8s", - prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i, buf.get(), - stateName(slot.mBufferState) - ); - - if (buf != NULL) { - result.appendFormat( - ", %p [%4ux%4u:%4u,%3X]", - buf->handle, buf->width, buf->height, buf->stride, - buf->format); - } - result.append("\n"); - } +status_t BufferQueue::setSidebandStream(const sp<NativeHandle>& stream) { + return mProducer->setSidebandStream(stream); } -void BufferQueue::freeBufferLocked(int slot) { - ST_LOGV("freeBufferLocked: slot=%d", slot); - mSlots[slot].mGraphicBuffer = 0; - if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) { - mSlots[slot].mNeedsCleanupOnRelease = true; - } - mSlots[slot].mBufferState = BufferSlot::FREE; - mSlots[slot].mFrameNumber = 0; - mSlots[slot].mAcquireCalled = false; - - // destroy fence as BufferQueue now takes ownership - if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) { - eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence); - mSlots[slot].mEglFence = EGL_NO_SYNC_KHR; - } - mSlots[slot].mFence = Fence::NO_FENCE; +status_t BufferQueue::acquireBuffer(BufferItem* buffer, nsecs_t presentWhen) { + return mConsumer->acquireBuffer(buffer, presentWhen); } -void BufferQueue::freeAllBuffersLocked() { - mBufferHasBeenQueued = false; - for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { - freeBufferLocked(i); - } +status_t BufferQueue::detachConsumerBuffer(int slot) { + return mConsumer->detachBuffer(slot); } -status_t BufferQueue::acquireBuffer(BufferItem *buffer, nsecs_t expectedPresent) { - ATRACE_CALL(); - Mutex::Autolock _l(mMutex); - - // Check that the consumer doesn't currently have the maximum number of - // buffers acquired. We allow the max buffer count to be exceeded by one - // buffer, so that the consumer can successfully set up the newly acquired - // buffer before releasing the old one. - int numAcquiredBuffers = 0; - for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { - if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) { - numAcquiredBuffers++; - } - } - if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) { - ST_LOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)", - numAcquiredBuffers, mMaxAcquiredBufferCount); - return INVALID_OPERATION; - } - - // check if queue is empty - // In asynchronous mode the list is guaranteed to be one buffer - // deep, while in synchronous mode we use the oldest buffer. - if (mQueue.empty()) { - return NO_BUFFER_AVAILABLE; - } - - Fifo::iterator front(mQueue.begin()); - - // If expectedPresent is specified, we may not want to return a buffer yet. - // If it's specified and there's more than one buffer queued, we may - // want to drop a buffer. - if (expectedPresent != 0) { - const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second - - // The "expectedPresent" argument indicates when the buffer is expected - // to be presented on-screen. If the buffer's desired-present time - // is earlier (less) than expectedPresent, meaning it'll be displayed - // on time or possibly late if we show it ASAP, we acquire and return - // it. If we don't want to display it until after the expectedPresent - // time, we return PRESENT_LATER without acquiring it. - // - // To be safe, we don't defer acquisition if expectedPresent is - // more than one second in the future beyond the desired present time - // (i.e. we'd be holding the buffer for a long time). - // - // NOTE: code assumes monotonic time values from the system clock are - // positive. - - // Start by checking to see if we can drop frames. We skip this check - // if the timestamps are being auto-generated by Surface -- if the - // app isn't generating timestamps explicitly, they probably don't - // want frames to be discarded based on them. - while (mQueue.size() > 1 && !mQueue[0].mIsAutoTimestamp) { - // If entry[1] is timely, drop entry[0] (and repeat). We apply - // an additional criteria here: we only drop the earlier buffer if - // our desiredPresent falls within +/- 1 second of the expected - // present. Otherwise, bogus desiredPresent times (e.g. 0 or - // a small relative timestamp), which normally mean "ignore the - // timestamp and acquire immediately", would cause us to drop - // frames. - // - // We may want to add an additional criteria: don't drop the - // earlier buffer if entry[1]'s fence hasn't signaled yet. - // - // (Vector front is [0], back is [size()-1]) - const BufferItem& bi(mQueue[1]); - nsecs_t desiredPresent = bi.mTimestamp; - if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC || - desiredPresent > expectedPresent) { - // This buffer is set to display in the near future, or - // desiredPresent is garbage. Either way we don't want to - // drop the previous buffer just to get this on screen sooner. - ST_LOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld", - desiredPresent, expectedPresent, desiredPresent - expectedPresent, - systemTime(CLOCK_MONOTONIC)); - break; - } - ST_LOGV("pts drop: queue1des=%lld expect=%lld size=%d", - desiredPresent, expectedPresent, mQueue.size()); - if (stillTracking(front)) { - // front buffer is still in mSlots, so mark the slot as free - mSlots[front->mBuf].mBufferState = BufferSlot::FREE; - } - mQueue.erase(front); - front = mQueue.begin(); - } - - // See if the front buffer is due. - nsecs_t desiredPresent = front->mTimestamp; - if (desiredPresent > expectedPresent && - desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) { - ST_LOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld", - desiredPresent, expectedPresent, desiredPresent - expectedPresent, - systemTime(CLOCK_MONOTONIC)); - return PRESENT_LATER; - } - - ST_LOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld", - desiredPresent, expectedPresent, desiredPresent - expectedPresent, - systemTime(CLOCK_MONOTONIC)); - } - - int buf = front->mBuf; - *buffer = *front; - ATRACE_BUFFER_INDEX(buf); - - ST_LOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }", - front->mBuf, front->mFrameNumber, - front->mGraphicBuffer->handle); - // if front buffer still being tracked update slot state - if (stillTracking(front)) { - mSlots[buf].mAcquireCalled = true; - mSlots[buf].mNeedsCleanupOnRelease = false; - mSlots[buf].mBufferState = BufferSlot::ACQUIRED; - mSlots[buf].mFence = Fence::NO_FENCE; - } - - // If the buffer has previously been acquired by the consumer, set - // mGraphicBuffer to NULL to avoid unnecessarily remapping this - // buffer on the consumer side. - if (buffer->mAcquireCalled) { - buffer->mGraphicBuffer = NULL; - } - - mQueue.erase(front); - mDequeueCondition.broadcast(); - - ATRACE_INT(mConsumerName.string(), mQueue.size()); - - return NO_ERROR; +status_t BufferQueue::attachConsumerBuffer(int* slot, + const sp<GraphicBuffer>& buffer) { + return mConsumer->attachBuffer(slot, buffer); } status_t BufferQueue::releaseBuffer( int buf, uint64_t frameNumber, EGLDisplay display, EGLSyncKHR eglFence, const sp<Fence>& fence) { - ATRACE_CALL(); - ATRACE_BUFFER_INDEX(buf); - - if (buf == INVALID_BUFFER_SLOT || fence == NULL) { - return BAD_VALUE; - } - - Mutex::Autolock _l(mMutex); - - // If the frame number has changed because buffer has been reallocated, - // we can ignore this releaseBuffer for the old buffer. - if (frameNumber != mSlots[buf].mFrameNumber) { - return STALE_BUFFER_SLOT; - } - - - // Internal state consistency checks: - // Make sure this buffers hasn't been queued while we were owning it (acquired) - Fifo::iterator front(mQueue.begin()); - Fifo::const_iterator const end(mQueue.end()); - while (front != end) { - if (front->mBuf == buf) { - LOG_ALWAYS_FATAL("[%s] received new buffer(#%lld) on slot #%d that has not yet been " - "acquired", mConsumerName.string(), frameNumber, buf); - break; // never reached - } - front++; - } - - // The buffer can now only be released if its in the acquired state - if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) { - mSlots[buf].mEglDisplay = display; - mSlots[buf].mEglFence = eglFence; - mSlots[buf].mFence = fence; - mSlots[buf].mBufferState = BufferSlot::FREE; - } else if (mSlots[buf].mNeedsCleanupOnRelease) { - ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState); - mSlots[buf].mNeedsCleanupOnRelease = false; - return STALE_BUFFER_SLOT; - } else { - ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState); - return -EINVAL; - } - - mDequeueCondition.broadcast(); - return NO_ERROR; + return mConsumer->releaseBuffer(buf, frameNumber, fence, display, eglFence); } status_t BufferQueue::consumerConnect(const sp<IConsumerListener>& consumerListener, bool controlledByApp) { - ST_LOGV("consumerConnect controlledByApp=%s", - controlledByApp ? "true" : "false"); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("consumerConnect: BufferQueue has been abandoned!"); - return NO_INIT; - } - if (consumerListener == NULL) { - ST_LOGE("consumerConnect: consumerListener may not be NULL"); - return BAD_VALUE; - } - - mConsumerListener = consumerListener; - mConsumerControlledByApp = controlledByApp; - - return NO_ERROR; + return mConsumer->connect(consumerListener, controlledByApp); } status_t BufferQueue::consumerDisconnect() { - ST_LOGV("consumerDisconnect"); - Mutex::Autolock lock(mMutex); - - if (mConsumerListener == NULL) { - ST_LOGE("consumerDisconnect: No consumer is connected!"); - return -EINVAL; - } - - mAbandoned = true; - mConsumerListener = NULL; - mQueue.clear(); - freeAllBuffersLocked(); - mDequeueCondition.broadcast(); - return NO_ERROR; + return mConsumer->disconnect(); } status_t BufferQueue::getReleasedBuffers(uint32_t* slotMask) { - ST_LOGV("getReleasedBuffers"); - Mutex::Autolock lock(mMutex); - - if (mAbandoned) { - ST_LOGE("getReleasedBuffers: BufferQueue has been abandoned!"); - return NO_INIT; - } - - uint32_t mask = 0; - for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { - if (!mSlots[i].mAcquireCalled) { - mask |= 1 << i; - } - } - - // Remove buffers in flight (on the queue) from the mask where acquire has - // been called, as the consumer will not receive the buffer address, so - // it should not free these slots. - Fifo::iterator front(mQueue.begin()); - while (front != mQueue.end()) { - if (front->mAcquireCalled) - mask &= ~(1 << front->mBuf); - front++; - } - - *slotMask = mask; - - ST_LOGV("getReleasedBuffers: returning mask %#x", mask); - return NO_ERROR; + return mConsumer->getReleasedBuffers(slotMask); } status_t BufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) { - ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h); - if (!w || !h) { - ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)", - w, h); - return BAD_VALUE; - } - - Mutex::Autolock lock(mMutex); - mDefaultWidth = w; - mDefaultHeight = h; - return NO_ERROR; + return mConsumer->setDefaultBufferSize(w, h); } status_t BufferQueue::setDefaultMaxBufferCount(int bufferCount) { - ATRACE_CALL(); - Mutex::Autolock lock(mMutex); - return setDefaultMaxBufferCountLocked(bufferCount); + return mConsumer->setDefaultMaxBufferCount(bufferCount); } status_t BufferQueue::disableAsyncBuffer() { - ATRACE_CALL(); - Mutex::Autolock lock(mMutex); - if (mConsumerListener != NULL) { - ST_LOGE("disableAsyncBuffer: consumer already connected!"); - return INVALID_OPERATION; - } - mUseAsyncBuffer = false; - return NO_ERROR; + return mConsumer->disableAsyncBuffer(); } status_t BufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) { - ATRACE_CALL(); - Mutex::Autolock lock(mMutex); - if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) { - ST_LOGE("setMaxAcquiredBufferCount: invalid count specified: %d", - maxAcquiredBuffers); - return BAD_VALUE; - } - if (mConnectedApi != NO_CONNECTED_API) { - return INVALID_OPERATION; - } - mMaxAcquiredBufferCount = maxAcquiredBuffers; - return NO_ERROR; + return mConsumer->setMaxAcquiredBufferCount(maxAcquiredBuffers); } -int BufferQueue::getMinUndequeuedBufferCount(bool async) const { - // if dequeueBuffer is allowed to error out, we don't have to - // add an extra buffer. - if (!mUseAsyncBuffer) - return mMaxAcquiredBufferCount; - - // we're in async mode, or we want to prevent the app to - // deadlock itself, we throw-in an extra buffer to guarantee it. - if (mDequeueBufferCannotBlock || async) - return mMaxAcquiredBufferCount+1; - - return mMaxAcquiredBufferCount; +void BufferQueue::setConsumerName(const String8& name) { + mConsumer->setConsumerName(name); } -int BufferQueue::getMinMaxBufferCountLocked(bool async) const { - return getMinUndequeuedBufferCount(async) + 1; +status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) { + return mConsumer->setDefaultBufferFormat(defaultFormat); } -int BufferQueue::getMaxBufferCountLocked(bool async) const { - int minMaxBufferCount = getMinMaxBufferCountLocked(async); - - int maxBufferCount = mDefaultMaxBufferCount; - if (maxBufferCount < minMaxBufferCount) { - maxBufferCount = minMaxBufferCount; - } - if (mOverrideMaxBufferCount != 0) { - assert(mOverrideMaxBufferCount >= minMaxBufferCount); - maxBufferCount = mOverrideMaxBufferCount; - } - - // Any buffers that are dequeued by the producer or sitting in the queue - // waiting to be consumed need to have their slots preserved. Such - // buffers will temporarily keep the max buffer count up until the slots - // no longer need to be preserved. - for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) { - BufferSlot::BufferState state = mSlots[i].mBufferState; - if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) { - maxBufferCount = i + 1; - } - } - - return maxBufferCount; +status_t BufferQueue::setConsumerUsageBits(uint32_t usage) { + return mConsumer->setConsumerUsageBits(usage); } -bool BufferQueue::stillTracking(const BufferItem *item) const { - const BufferSlot &slot = mSlots[item->mBuf]; - - ST_LOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, " - "slot: { slot=%d/%llu, buffer=%p }", - item->mBuf, item->mFrameNumber, - (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0), - item->mBuf, slot.mFrameNumber, - (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0)); - - // Compare item with its original buffer slot. We can check the slot - // as the buffer would not be moved to a different slot by the producer. - return (slot.mGraphicBuffer != NULL && - item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle); +status_t BufferQueue::setTransformHint(uint32_t hint) { + return mConsumer->setTransformHint(hint); } -BufferQueue::ProxyConsumerListener::ProxyConsumerListener( - const wp<ConsumerListener>& consumerListener): - mConsumerListener(consumerListener) {} - -BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {} +sp<NativeHandle> BufferQueue::getSidebandStream() const { + return mConsumer->getSidebandStream(); +} -void BufferQueue::ProxyConsumerListener::onFrameAvailable() { - sp<ConsumerListener> listener(mConsumerListener.promote()); - if (listener != NULL) { - listener->onFrameAvailable(); - } +void BufferQueue::dump(String8& result, const char* prefix) const { + mConsumer->dump(result, prefix); } -void BufferQueue::ProxyConsumerListener::onBuffersReleased() { +void BufferQueue::ProxyConsumerListener::onSidebandStreamChanged() { sp<ConsumerListener> listener(mConsumerListener.promote()); if (listener != NULL) { - listener->onBuffersReleased(); + listener->onSidebandStreamChanged(); } } diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp new file mode 100644 index 0000000..756cd61 --- /dev/null +++ b/libs/gui/BufferQueueConsumer.cpp @@ -0,0 +1,489 @@ +/* + * Copyright 2014 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. + */ + +#define LOG_TAG "BufferQueueConsumer" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#include <gui/BufferItem.h> +#include <gui/BufferQueueConsumer.h> +#include <gui/BufferQueueCore.h> +#include <gui/IConsumerListener.h> + +namespace android { + +BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) : + mCore(core), + mSlots(core->mSlots), + mConsumerName() {} + +BufferQueueConsumer::~BufferQueueConsumer() {} + +status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, + nsecs_t expectedPresent) { + ATRACE_CALL(); + Mutex::Autolock lock(mCore->mMutex); + + // Check that the consumer doesn't currently have the maximum number of + // buffers acquired. We allow the max buffer count to be exceeded by one + // buffer so that the consumer can successfully set up the newly acquired + // buffer before releasing the old one. + int numAcquiredBuffers = 0; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) { + ++numAcquiredBuffers; + } + } + if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) { + BQ_LOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)", + numAcquiredBuffers, mCore->mMaxAcquiredBufferCount); + return INVALID_OPERATION; + } + + // Check if the queue is empty. + // In asynchronous mode the list is guaranteed to be one buffer deep, + // while in synchronous mode we use the oldest buffer. + if (mCore->mQueue.empty()) { + return NO_BUFFER_AVAILABLE; + } + + BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin()); + + // If expectedPresent is specified, we may not want to return a buffer yet. + // If it's specified and there's more than one buffer queued, we may want + // to drop a buffer. + if (expectedPresent != 0) { + const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second + + // The 'expectedPresent' argument indicates when the buffer is expected + // to be presented on-screen. If the buffer's desired present time is + // earlier (less) than expectedPresent -- meaning it will be displayed + // on time or possibly late if we show it as soon as possible -- we + // acquire and return it. If we don't want to display it until after the + // expectedPresent time, we return PRESENT_LATER without acquiring it. + // + // To be safe, we don't defer acquisition if expectedPresent is more + // than one second in the future beyond the desired present time + // (i.e., we'd be holding the buffer for a long time). + // + // NOTE: Code assumes monotonic time values from the system clock + // are positive. + + // Start by checking to see if we can drop frames. We skip this check if + // the timestamps are being auto-generated by Surface. If the app isn't + // generating timestamps explicitly, it probably doesn't want frames to + // be discarded based on them. + while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) { + // If entry[1] is timely, drop entry[0] (and repeat). We apply an + // additional criterion here: we only drop the earlier buffer if our + // desiredPresent falls within +/- 1 second of the expected present. + // Otherwise, bogus desiredPresent times (e.g., 0 or a small + // relative timestamp), which normally mean "ignore the timestamp + // and acquire immediately", would cause us to drop frames. + // + // We may want to add an additional criterion: don't drop the + // earlier buffer if entry[1]'s fence hasn't signaled yet. + const BufferItem& bufferItem(mCore->mQueue[1]); + nsecs_t desiredPresent = bufferItem.mTimestamp; + if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC || + desiredPresent > expectedPresent) { + // This buffer is set to display in the near future, or + // desiredPresent is garbage. Either way we don't want to drop + // the previous buffer just to get this on the screen sooner. + BQ_LOGV("acquireBuffer: nodrop desire=%lld expect=%lld " + "(%lld) now=%lld", desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + break; + } + + BQ_LOGV("acquireBuffer: drop desire=%lld expect=%lld size=%d", + desiredPresent, expectedPresent, mCore->mQueue.size()); + if (mCore->stillTracking(front)) { + // Front buffer is still in mSlots, so mark the slot as free + mSlots[front->mSlot].mBufferState = BufferSlot::FREE; + } + mCore->mQueue.erase(front); + front = mCore->mQueue.begin(); + } + + // See if the front buffer is due + nsecs_t desiredPresent = front->mTimestamp; + if (desiredPresent > expectedPresent && + desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) { + BQ_LOGV("acquireBuffer: defer desire=%lld expect=%lld " + "(%lld) now=%lld", desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + return PRESENT_LATER; + } + + BQ_LOGV("acquireBuffer: accept desire=%lld expect=%lld " + "(%lld) now=%lld", desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + } + + int slot = front->mSlot; + *outBuffer = *front; + ATRACE_BUFFER_INDEX(slot); + + BQ_LOGV("acquireBuffer: acquiring { slot=%d/%llu buffer=%p }", + slot, front->mFrameNumber, front->mGraphicBuffer->handle); + // If the front buffer is still being tracked, update its slot state + if (mCore->stillTracking(front)) { + mSlots[slot].mAcquireCalled = true; + mSlots[slot].mNeedsCleanupOnRelease = false; + mSlots[slot].mBufferState = BufferSlot::ACQUIRED; + mSlots[slot].mFence = Fence::NO_FENCE; + } + + // If the buffer has previously been acquired by the consumer, set + // mGraphicBuffer to NULL to avoid unnecessarily remapping this buffer + // on the consumer side + if (outBuffer->mAcquireCalled) { + outBuffer->mGraphicBuffer = NULL; + } + + mCore->mQueue.erase(front); + // TODO: Should this call be after we free a slot while dropping buffers? + // Simply acquiring the next buffer doesn't enable a producer to dequeue. + mCore->mDequeueCondition.broadcast(); + + ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size()); + + return NO_ERROR; +} + +status_t BufferQueueConsumer::detachBuffer(int slot) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(slot); + BQ_LOGV("detachBuffer(C): slot %d", slot); + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("detachBuffer(C): BufferQueue has been abandoned"); + return NO_INIT; + } + + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGE("detachBuffer(C): slot index %d out of range [0, %d)", + slot, BufferQueueDefs::NUM_BUFFER_SLOTS); + return BAD_VALUE; + } else if (mSlots[slot].mBufferState != BufferSlot::ACQUIRED) { + BQ_LOGE("detachBuffer(C): slot %d is not owned by the consumer " + "(state = %d)", slot, mSlots[slot].mBufferState); + return BAD_VALUE; + } + + mCore->freeBufferLocked(slot); + mCore->mDequeueCondition.broadcast(); + + return NO_ERROR; +} + +status_t BufferQueueConsumer::attachBuffer(int* outSlot, + const sp<android::GraphicBuffer>& buffer) { + ATRACE_CALL(); + + if (outSlot == NULL) { + BQ_LOGE("attachBuffer(P): outSlot must not be NULL"); + return BAD_VALUE; + } else if (buffer == NULL) { + BQ_LOGE("attachBuffer(P): cannot attach NULL buffer"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + + // Make sure we don't have too many acquired buffers and find a free slot + // to put the buffer into (the oldest if there are multiple). + int numAcquiredBuffers = 0; + int found = BufferQueueCore::INVALID_BUFFER_SLOT; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) { + ++numAcquiredBuffers; + } else if (mSlots[s].mBufferState == BufferSlot::FREE) { + if (found == BufferQueueCore::INVALID_BUFFER_SLOT || + mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) { + found = s; + } + } + } + + if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) { + BQ_LOGE("attachBuffer(P): max acquired buffer count reached: %d " + "(max %d)", numAcquiredBuffers, + mCore->mMaxAcquiredBufferCount); + return INVALID_OPERATION; + } + if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { + BQ_LOGE("attachBuffer(P): could not find free buffer slot"); + return NO_MEMORY; + } + + *outSlot = found; + ATRACE_BUFFER_INDEX(*outSlot); + BQ_LOGV("attachBuffer(C): returning slot %d", *outSlot); + + mSlots[*outSlot].mGraphicBuffer = buffer; + mSlots[*outSlot].mBufferState = BufferSlot::ACQUIRED; + mSlots[*outSlot].mAttachedByConsumer = true; + mSlots[*outSlot].mAcquireCalled = true; + mSlots[*outSlot].mNeedsCleanupOnRelease = false; + mSlots[*outSlot].mFence = Fence::NO_FENCE; + mSlots[*outSlot].mFrameNumber = 0; + + return NO_ERROR; +} + +status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber, + const sp<Fence>& releaseFence, EGLDisplay eglDisplay, + EGLSyncKHR eglFence) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(slot); + + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS || + releaseFence == NULL) { + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + + // If the frame number has changed because the buffer has been reallocated, + // we can ignore this releaseBuffer for the old buffer + if (frameNumber != mSlots[slot].mFrameNumber) { + return STALE_BUFFER_SLOT; + } + + // Make sure this buffer hasn't been queued while acquired by the consumer + BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin()); + while (current != mCore->mQueue.end()) { + if (current->mSlot == slot) { + BQ_LOGE("releaseBuffer: buffer slot %d pending release is " + "currently queued", slot); + return BAD_VALUE; + } + ++current; + } + + if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) { + mSlots[slot].mEglDisplay = eglDisplay; + mSlots[slot].mEglFence = eglFence; + mSlots[slot].mFence = releaseFence; + mSlots[slot].mBufferState = BufferSlot::FREE; + } else if (mSlots[slot].mNeedsCleanupOnRelease) { + BQ_LOGV("releaseBuffer: releasing a stale buffer slot %d " + "(state = %d)", slot, mSlots[slot].mBufferState); + mSlots[slot].mNeedsCleanupOnRelease = false; + return STALE_BUFFER_SLOT; + } else { + BQ_LOGV("releaseBuffer: attempted to release buffer slot %d " + "but its state was %d", slot, mSlots[slot].mBufferState); + return BAD_VALUE; + } + + mCore->mDequeueCondition.broadcast(); + + return NO_ERROR; +} + +status_t BufferQueueConsumer::connect( + const sp<IConsumerListener>& consumerListener, bool controlledByApp) { + ATRACE_CALL(); + + if (consumerListener == NULL) { + BQ_LOGE("connect(C): consumerListener may not be NULL"); + return BAD_VALUE; + } + + BQ_LOGV("connect(C): controlledByApp=%s", + controlledByApp ? "true" : "false"); + + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("connect(C): BufferQueue has been abandoned"); + return NO_INIT; + } + + mCore->mConsumerListener = consumerListener; + mCore->mConsumerControlledByApp = controlledByApp; + + return NO_ERROR; +} + +status_t BufferQueueConsumer::disconnect() { + ATRACE_CALL(); + + BQ_LOGV("disconnect(C)"); + + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mConsumerListener == NULL) { + BQ_LOGE("disconnect(C): no consumer is connected"); + return BAD_VALUE; + } + + mCore->mIsAbandoned = true; + mCore->mConsumerListener = NULL; + mCore->mQueue.clear(); + mCore->freeAllBuffersLocked(); + mCore->mDequeueCondition.broadcast(); + return NO_ERROR; +} + +status_t BufferQueueConsumer::getReleasedBuffers(uint32_t *outSlotMask) { + ATRACE_CALL(); + + if (outSlotMask == NULL) { + BQ_LOGE("getReleasedBuffers: outSlotMask may not be NULL"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("getReleasedBuffers: BufferQueue has been abandoned"); + return NO_INIT; + } + + uint32_t mask = 0; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (!mSlots[s].mAcquireCalled) { + mask |= (1u << s); + } + } + + // Remove from the mask queued buffers for which acquire has been called, + // since the consumer will not receive their buffer addresses and so must + // retain their cached information + BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin()); + while (current != mCore->mQueue.end()) { + if (current->mAcquireCalled) { + mask &= ~(1u << current->mSlot); + } + ++current; + } + + BQ_LOGV("getReleasedBuffers: returning mask %#x", mask); + *outSlotMask = mask; + return NO_ERROR; +} + +status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width, + uint32_t height) { + ATRACE_CALL(); + + if (width == 0 || height == 0) { + BQ_LOGV("setDefaultBufferSize: dimensions cannot be 0 (width=%u " + "height=%u)", width, height); + return BAD_VALUE; + } + + BQ_LOGV("setDefaultBufferSize: width=%u height=%u", width, height); + + Mutex::Autolock lock(mCore->mMutex); + mCore->mDefaultWidth = width; + mCore->mDefaultHeight = height; + return NO_ERROR; +} + +status_t BufferQueueConsumer::setDefaultMaxBufferCount(int bufferCount) { + ATRACE_CALL(); + Mutex::Autolock lock(mCore->mMutex); + return mCore->setDefaultMaxBufferCountLocked(bufferCount); +} + +status_t BufferQueueConsumer::disableAsyncBuffer() { + ATRACE_CALL(); + + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mConsumerListener != NULL) { + BQ_LOGE("disableAsyncBuffer: consumer already connected"); + return INVALID_OPERATION; + } + + BQ_LOGV("disableAsyncBuffer"); + mCore->mUseAsyncBuffer = false; + return NO_ERROR; +} + +status_t BufferQueueConsumer::setMaxAcquiredBufferCount( + int maxAcquiredBuffers) { + ATRACE_CALL(); + + if (maxAcquiredBuffers < 1 || + maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) { + BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d", + maxAcquiredBuffers); + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) { + BQ_LOGE("setMaxAcquiredBufferCount: producer is already connected"); + return INVALID_OPERATION; + } + + BQ_LOGV("setMaxAcquiredBufferCount: %d", maxAcquiredBuffers); + mCore->mMaxAcquiredBufferCount = maxAcquiredBuffers; + return NO_ERROR; +} + +void BufferQueueConsumer::setConsumerName(const String8& name) { + ATRACE_CALL(); + BQ_LOGV("setConsumerName: '%s'", name.string()); + Mutex::Autolock lock(mCore->mMutex); + mCore->mConsumerName = name; + mConsumerName = name; +} + +status_t BufferQueueConsumer::setDefaultBufferFormat(uint32_t defaultFormat) { + ATRACE_CALL(); + BQ_LOGV("setDefaultBufferFormat: %u", defaultFormat); + Mutex::Autolock lock(mCore->mMutex); + mCore->mDefaultBufferFormat = defaultFormat; + return NO_ERROR; +} + +status_t BufferQueueConsumer::setConsumerUsageBits(uint32_t usage) { + ATRACE_CALL(); + BQ_LOGV("setConsumerUsageBits: %#x", usage); + Mutex::Autolock lock(mCore->mMutex); + mCore->mConsumerUsageBits = usage; + return NO_ERROR; +} + +status_t BufferQueueConsumer::setTransformHint(uint32_t hint) { + ATRACE_CALL(); + BQ_LOGV("setTransformHint: %#x", hint); + Mutex::Autolock lock(mCore->mMutex); + mCore->mTransformHint = hint; + return NO_ERROR; +} + +sp<NativeHandle> BufferQueueConsumer::getSidebandStream() const { + return mCore->mSidebandStream; +} + +void BufferQueueConsumer::dump(String8& result, const char* prefix) const { + mCore->dump(result, prefix); +} + +} // namespace android diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp new file mode 100644 index 0000000..40a6cbb --- /dev/null +++ b/libs/gui/BufferQueueCore.cpp @@ -0,0 +1,227 @@ +/* + * Copyright 2014 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. + */ + +#define LOG_TAG "BufferQueueCore" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#define EGL_EGLEXT_PROTOTYPES + +#include <inttypes.h> + +#include <gui/BufferItem.h> +#include <gui/BufferQueueCore.h> +#include <gui/IConsumerListener.h> +#include <gui/IGraphicBufferAlloc.h> +#include <gui/ISurfaceComposer.h> +#include <private/gui/ComposerService.h> + +template <typename T> +static inline T max(T a, T b) { return a > b ? a : b; } + +namespace android { + +static String8 getUniqueName() { + static volatile int32_t counter = 0; + return String8::format("unnamed-%d-%d", getpid(), + android_atomic_inc(&counter)); +} + +BufferQueueCore::BufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) : + mAllocator(allocator), + mMutex(), + mIsAbandoned(false), + mConsumerControlledByApp(false), + mConsumerName(getUniqueName()), + mConsumerListener(), + mConsumerUsageBits(0), + mConnectedApi(NO_CONNECTED_API), + mConnectedProducerToken(), + mSlots(), + mQueue(), + mOverrideMaxBufferCount(0), + mDequeueCondition(), + mUseAsyncBuffer(true), + mDequeueBufferCannotBlock(false), + mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888), + mDefaultWidth(1), + mDefaultHeight(1), + mDefaultMaxBufferCount(2), + mMaxAcquiredBufferCount(1), + mBufferHasBeenQueued(false), + mFrameCounter(0), + mTransformHint(0) +{ + if (allocator == NULL) { + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + mAllocator = composer->createGraphicBufferAlloc(); + if (mAllocator == NULL) { + BQ_LOGE("createGraphicBufferAlloc failed"); + } + } +} + +BufferQueueCore::~BufferQueueCore() {} + +void BufferQueueCore::dump(String8& result, const char* prefix) const { + Mutex::Autolock lock(mMutex); + + String8 fifo; + Fifo::const_iterator current(mQueue.begin()); + while (current != mQueue.end()) { + fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], " + "xform=0x%02x, time=%#" PRIx64 ", scale=%s\n", + current->mSlot, current->mGraphicBuffer.get(), + current->mCrop.left, current->mCrop.top, current->mCrop.right, + current->mCrop.bottom, current->mTransform, current->mTimestamp, + BufferItem::scalingModeName(current->mScalingMode)); + ++current; + } + + result.appendFormat("%s-BufferQueue mMaxAcquiredBufferCount=%d, " + "mDequeueBufferCannotBlock=%d, default-size=[%dx%d], " + "default-format=%d, transform-hint=%02x, FIFO(%zu)={%s}\n", + prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock, + mDefaultWidth, mDefaultHeight, mDefaultBufferFormat, mTransformHint, + mQueue.size(), fifo.string()); + + // Trim the free buffers so as to not spam the dump + int maxBufferCount = 0; + for (int s = BufferQueueDefs::NUM_BUFFER_SLOTS - 1; s >= 0; --s) { + const BufferSlot& slot(mSlots[s]); + if (slot.mBufferState != BufferSlot::FREE || + slot.mGraphicBuffer != NULL) { + maxBufferCount = s + 1; + break; + } + } + + for (int s = 0; s < maxBufferCount; ++s) { + const BufferSlot& slot(mSlots[s]); + const sp<GraphicBuffer>& buffer(slot.mGraphicBuffer); + result.appendFormat("%s%s[%02d:%p] state=%-8s", prefix, + (slot.mBufferState == BufferSlot::ACQUIRED) ? ">" : " ", + s, buffer.get(), + BufferSlot::bufferStateName(slot.mBufferState)); + + if (buffer != NULL) { + result.appendFormat(", %p [%4ux%4u:%4u,%3X]", buffer->handle, + buffer->width, buffer->height, buffer->stride, + buffer->format); + } + + result.append("\n"); + } +} + +int BufferQueueCore::getMinUndequeuedBufferCountLocked(bool async) const { + // If dequeueBuffer is allowed to error out, we don't have to add an + // extra buffer. + if (!mUseAsyncBuffer) { + return mMaxAcquiredBufferCount; + } + + if (mDequeueBufferCannotBlock || async) { + return mMaxAcquiredBufferCount + 1; + } + + return mMaxAcquiredBufferCount; +} + +int BufferQueueCore::getMinMaxBufferCountLocked(bool async) const { + return getMinUndequeuedBufferCountLocked(async) + 1; +} + +int BufferQueueCore::getMaxBufferCountLocked(bool async) const { + int minMaxBufferCount = getMinMaxBufferCountLocked(async); + + int maxBufferCount = max(mDefaultMaxBufferCount, minMaxBufferCount); + if (mOverrideMaxBufferCount != 0) { + assert(mOverrideMaxBufferCount >= minMaxBufferCount); + maxBufferCount = mOverrideMaxBufferCount; + } + + // Any buffers that are dequeued by the producer or sitting in the queue + // waiting to be consumed need to have their slots preserved. Such buffers + // will temporarily keep the max buffer count up until the slots no longer + // need to be preserved. + for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + BufferSlot::BufferState state = mSlots[s].mBufferState; + if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) { + maxBufferCount = s + 1; + } + } + + return maxBufferCount; +} + +status_t BufferQueueCore::setDefaultMaxBufferCountLocked(int count) { + const int minBufferCount = mUseAsyncBuffer ? 2 : 1; + if (count < minBufferCount || count > BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGV("setDefaultMaxBufferCount: invalid count %d, should be in " + "[%d, %d]", minBufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS); + return BAD_VALUE; + } + + BQ_LOGV("setDefaultMaxBufferCount: setting count to %d", count); + mDefaultMaxBufferCount = count; + mDequeueCondition.broadcast(); + + return NO_ERROR; +} + +void BufferQueueCore::freeBufferLocked(int slot) { + BQ_LOGV("freeBufferLocked: slot %d", slot); + mSlots[slot].mGraphicBuffer.clear(); + if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) { + mSlots[slot].mNeedsCleanupOnRelease = true; + } + mSlots[slot].mBufferState = BufferSlot::FREE; + mSlots[slot].mFrameNumber = 0; + mSlots[slot].mAcquireCalled = false; + + // Destroy fence as BufferQueue now takes ownership + if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence); + mSlots[slot].mEglFence = EGL_NO_SYNC_KHR; + } + mSlots[slot].mFence = Fence::NO_FENCE; +} + +void BufferQueueCore::freeAllBuffersLocked() { + mBufferHasBeenQueued = false; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + freeBufferLocked(s); + } +} + +bool BufferQueueCore::stillTracking(const BufferItem* item) const { + const BufferSlot& slot = mSlots[item->mSlot]; + + BQ_LOGV("stillTracking: item { slot=%d/%llu buffer=%p } " + "slot { slot=%d/%llu buffer=%p }", + item->mSlot, item->mFrameNumber, + (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0), + item->mSlot, slot.mFrameNumber, + (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0)); + + // Compare item with its original buffer slot. We can check the slot as + // the buffer would not be moved to a different slot by the producer. + return (slot.mGraphicBuffer != NULL) && + (item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle); +} + +} // namespace android diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp new file mode 100644 index 0000000..9dd90ba --- /dev/null +++ b/libs/gui/BufferQueueProducer.cpp @@ -0,0 +1,810 @@ +/* + * Copyright 2014 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. + */ + +#define LOG_TAG "BufferQueueProducer" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#define EGL_EGLEXT_PROTOTYPES + +#include <gui/BufferItem.h> +#include <gui/BufferQueueCore.h> +#include <gui/BufferQueueProducer.h> +#include <gui/IConsumerListener.h> +#include <gui/IGraphicBufferAlloc.h> + +#include <utils/Log.h> +#include <utils/Trace.h> + +namespace android { + +BufferQueueProducer::BufferQueueProducer(const sp<BufferQueueCore>& core) : + mCore(core), + mSlots(core->mSlots), + mConsumerName() {} + +BufferQueueProducer::~BufferQueueProducer() {} + +status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) { + ATRACE_CALL(); + BQ_LOGV("requestBuffer: slot %d", slot); + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("requestBuffer: BufferQueue has been abandoned"); + return NO_INIT; + } + + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)", + slot, BufferQueueDefs::NUM_BUFFER_SLOTS); + return BAD_VALUE; + } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { + BQ_LOGE("requestBuffer: slot %d is not owned by the producer " + "(state = %d)", slot, mSlots[slot].mBufferState); + return BAD_VALUE; + } + + mSlots[slot].mRequestBufferCalled = true; + *buf = mSlots[slot].mGraphicBuffer; + return NO_ERROR; +} + +status_t BufferQueueProducer::setBufferCount(int bufferCount) { + ATRACE_CALL(); + BQ_LOGV("setBufferCount: count = %d", bufferCount); + + sp<IConsumerListener> listener; + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("setBufferCount: BufferQueue has been abandoned"); + return NO_INIT; + } + + if (bufferCount > BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGE("setBufferCount: bufferCount %d too large (max %d)", + bufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS); + return BAD_VALUE; + } + + // There must be no dequeued buffers when changing the buffer count. + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (mSlots[s].mBufferState == BufferSlot::DEQUEUED) { + BQ_LOGE("setBufferCount: buffer owned by producer"); + return BAD_VALUE; + } + } + + if (bufferCount == 0) { + mCore->mOverrideMaxBufferCount = 0; + mCore->mDequeueCondition.broadcast(); + return NO_ERROR; + } + + const int minBufferSlots = mCore->getMinMaxBufferCountLocked(false); + if (bufferCount < minBufferSlots) { + BQ_LOGE("setBufferCount: requested buffer count %d is less than " + "minimum %d", bufferCount, minBufferSlots); + return BAD_VALUE; + } + + // Here we are guaranteed that the producer doesn't have any dequeued + // buffers and will release all of its buffer references. We don't + // clear the queue, however, so that currently queued buffers still + // get displayed. + mCore->freeAllBuffersLocked(); + mCore->mOverrideMaxBufferCount = bufferCount; + mCore->mDequeueCondition.broadcast(); + listener = mCore->mConsumerListener; + } // Autolock scope + + // Call back without lock held + if (listener != NULL) { + listener->onBuffersReleased(); + } + + return NO_ERROR; +} + +status_t BufferQueueProducer::waitForFreeSlotThenRelock(const char* caller, + bool async, int* found, status_t* returnFlags) const { + bool tryAgain = true; + while (tryAgain) { + if (mCore->mIsAbandoned) { + BQ_LOGE("%s: BufferQueue has been abandoned", caller); + return NO_INIT; + } + + const int maxBufferCount = mCore->getMaxBufferCountLocked(async); + if (async && mCore->mOverrideMaxBufferCount) { + // FIXME: Some drivers are manually setting the buffer count + // (which they shouldn't), so we do this extra test here to + // handle that case. This is TEMPORARY until we get this fixed. + if (mCore->mOverrideMaxBufferCount < maxBufferCount) { + BQ_LOGE("%s: async mode is invalid with buffer count override", + caller); + return BAD_VALUE; + } + } + + // Free up any buffers that are in slots beyond the max buffer count + for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + assert(mSlots[s].mBufferState == BufferSlot::FREE); + if (mSlots[s].mGraphicBuffer != NULL) { + mCore->freeBufferLocked(s); + *returnFlags |= RELEASE_ALL_BUFFERS; + } + } + + // Look for a free buffer to give to the client + *found = BufferQueueCore::INVALID_BUFFER_SLOT; + int dequeuedCount = 0; + int acquiredCount = 0; + for (int s = 0; s < maxBufferCount; ++s) { + switch (mSlots[s].mBufferState) { + case BufferSlot::DEQUEUED: + ++dequeuedCount; + break; + case BufferSlot::ACQUIRED: + ++acquiredCount; + break; + case BufferSlot::FREE: + // We return the oldest of the free buffers to avoid + // stalling the producer if possible, since the consumer + // may still have pending reads of in-flight buffers + if (*found == BufferQueueCore::INVALID_BUFFER_SLOT || + mSlots[s].mFrameNumber < mSlots[*found].mFrameNumber) { + *found = s; + } + break; + default: + break; + } + } + + // Producers are not allowed to dequeue more than one buffer if they + // did not set a buffer count + if (!mCore->mOverrideMaxBufferCount && dequeuedCount) { + BQ_LOGE("%s: can't dequeue multiple buffers without setting the " + "buffer count", caller); + return INVALID_OPERATION; + } + + // See whether a buffer has been queued since the last + // setBufferCount so we know whether to perform the min undequeued + // buffers check below + if (mCore->mBufferHasBeenQueued) { + // Make sure the producer is not trying to dequeue more buffers + // than allowed + const int newUndequeuedCount = + maxBufferCount - (dequeuedCount + 1); + const int minUndequeuedCount = + mCore->getMinUndequeuedBufferCountLocked(async); + if (newUndequeuedCount < minUndequeuedCount) { + BQ_LOGE("%s: min undequeued buffer count (%d) exceeded " + "(dequeued=%d undequeued=%d)", + caller, minUndequeuedCount, + dequeuedCount, newUndequeuedCount); + return INVALID_OPERATION; + } + } + + // If no buffer is found, wait for a buffer to be released or for + // the max buffer count to change + tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT); + if (tryAgain) { + // Return an error if we're in non-blocking mode (producer and + // consumer are controlled by the application). + // However, the consumer is allowed to briefly acquire an extra + // buffer (which could cause us to have to wait here), which is + // okay, since it is only used to implement an atomic acquire + + // release (e.g., in GLConsumer::updateTexImage()) + if (mCore->mDequeueBufferCannotBlock && + (acquiredCount <= mCore->mMaxAcquiredBufferCount)) { + return WOULD_BLOCK; + } + mCore->mDequeueCondition.wait(mCore->mMutex); + } + } // while (tryAgain) + + return NO_ERROR; +} + +status_t BufferQueueProducer::dequeueBuffer(int *outSlot, + sp<android::Fence> *outFence, bool async, + uint32_t width, uint32_t height, uint32_t format, uint32_t usage) { + ATRACE_CALL(); + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + mConsumerName = mCore->mConsumerName; + } // Autolock scope + + BQ_LOGV("dequeueBuffer: async=%s w=%u h=%u format=%#x, usage=%#x", + async ? "true" : "false", width, height, format, usage); + + if ((width && !height) || (!width && height)) { + BQ_LOGE("dequeueBuffer: invalid size: w=%u h=%u", width, height); + return BAD_VALUE; + } + + status_t returnFlags = NO_ERROR; + EGLDisplay eglDisplay = EGL_NO_DISPLAY; + EGLSyncKHR eglFence = EGL_NO_SYNC_KHR; + bool attachedByConsumer = false; + + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + + if (format == 0) { + format = mCore->mDefaultBufferFormat; + } + + // Enable the usage bits the consumer requested + usage |= mCore->mConsumerUsageBits; + + int found; + status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async, + &found, &returnFlags); + if (status != NO_ERROR) { + return status; + } + + // This should not happen + if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { + BQ_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } + + *outSlot = found; + ATRACE_BUFFER_INDEX(found); + + attachedByConsumer = mSlots[found].mAttachedByConsumer; + + const bool useDefaultSize = !width && !height; + if (useDefaultSize) { + width = mCore->mDefaultWidth; + height = mCore->mDefaultHeight; + } + + mSlots[found].mBufferState = BufferSlot::DEQUEUED; + + const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer); + if ((buffer == NULL) || + (static_cast<uint32_t>(buffer->width) != width) || + (static_cast<uint32_t>(buffer->height) != height) || + (static_cast<uint32_t>(buffer->format) != format) || + ((static_cast<uint32_t>(buffer->usage) & usage) != usage)) + { + mSlots[found].mAcquireCalled = false; + mSlots[found].mGraphicBuffer = NULL; + mSlots[found].mRequestBufferCalled = false; + mSlots[found].mEglDisplay = EGL_NO_DISPLAY; + mSlots[found].mEglFence = EGL_NO_SYNC_KHR; + mSlots[found].mFence = Fence::NO_FENCE; + + returnFlags |= BUFFER_NEEDS_REALLOCATION; + } + + if (CC_UNLIKELY(mSlots[found].mFence == NULL)) { + BQ_LOGE("dequeueBuffer: about to return a NULL fence - " + "slot=%d w=%d h=%d format=%u", + found, buffer->width, buffer->height, buffer->format); + } + + eglDisplay = mSlots[found].mEglDisplay; + eglFence = mSlots[found].mEglFence; + *outFence = mSlots[found].mFence; + mSlots[found].mEglFence = EGL_NO_SYNC_KHR; + mSlots[found].mFence = Fence::NO_FENCE; + } // Autolock scope + + if (returnFlags & BUFFER_NEEDS_REALLOCATION) { + status_t error; + sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer( + width, height, format, usage, &error)); + if (graphicBuffer == NULL) { + BQ_LOGE("dequeueBuffer: createGraphicBuffer failed"); + return error; + } + + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("dequeueBuffer: BufferQueue has been abandoned"); + return NO_INIT; + } + + mSlots[*outSlot].mFrameNumber = UINT32_MAX; + mSlots[*outSlot].mGraphicBuffer = graphicBuffer; + } // Autolock scope + } + + if (attachedByConsumer) { + returnFlags |= BUFFER_NEEDS_REALLOCATION; + } + + if (eglFence != EGL_NO_SYNC_KHR) { + EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0, + 1000000000); + // If something goes wrong, log the error, but return the buffer without + // synchronizing access to it. It's too late at this point to abort the + // dequeue operation. + if (result == EGL_FALSE) { + BQ_LOGE("dequeueBuffer: error %#x waiting for fence", + eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + BQ_LOGE("dequeueBuffer: timeout waiting for fence"); + } + eglDestroySyncKHR(eglDisplay, eglFence); + } + + BQ_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outSlot, + mSlots[*outSlot].mFrameNumber, + mSlots[*outSlot].mGraphicBuffer->handle, returnFlags); + + return returnFlags; +} + +status_t BufferQueueProducer::detachBuffer(int slot) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(slot); + BQ_LOGV("detachBuffer(P): slot %d", slot); + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("detachBuffer(P): BufferQueue has been abandoned"); + return NO_INIT; + } + + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGE("detachBuffer(P): slot index %d out of range [0, %d)", + slot, BufferQueueDefs::NUM_BUFFER_SLOTS); + return BAD_VALUE; + } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { + BQ_LOGE("detachBuffer(P): slot %d is not owned by the producer " + "(state = %d)", slot, mSlots[slot].mBufferState); + return BAD_VALUE; + } else if (!mSlots[slot].mRequestBufferCalled) { + BQ_LOGE("detachBuffer(P): buffer in slot %d has not been requested", + slot); + return BAD_VALUE; + } + + mCore->freeBufferLocked(slot); + mCore->mDequeueCondition.broadcast(); + + return NO_ERROR; +} + +status_t BufferQueueProducer::attachBuffer(int* outSlot, + const sp<android::GraphicBuffer>& buffer) { + ATRACE_CALL(); + + if (outSlot == NULL) { + BQ_LOGE("attachBuffer(P): outSlot must not be NULL"); + return BAD_VALUE; + } else if (buffer == NULL) { + BQ_LOGE("attachBuffer(P): cannot attach NULL buffer"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + + status_t returnFlags = NO_ERROR; + int found; + // TODO: Should we provide an async flag to attachBuffer? It seems + // unlikely that buffers which we are attaching to a BufferQueue will + // be asynchronous (droppable), but it may not be impossible. + status_t status = waitForFreeSlotThenRelock("attachBuffer(P)", false, + &found, &returnFlags); + if (status != NO_ERROR) { + return status; + } + + // This should not happen + if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { + BQ_LOGE("attachBuffer(P): no available buffer slots"); + return -EBUSY; + } + + *outSlot = found; + ATRACE_BUFFER_INDEX(*outSlot); + BQ_LOGV("attachBuffer(P): returning slot %d flags=%#x", + *outSlot, returnFlags); + + mSlots[*outSlot].mGraphicBuffer = buffer; + mSlots[*outSlot].mBufferState = BufferSlot::DEQUEUED; + mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR; + mSlots[*outSlot].mFence = Fence::NO_FENCE; + mSlots[*outSlot].mRequestBufferCalled = true; + + return returnFlags; +} + +status_t BufferQueueProducer::queueBuffer(int slot, + const QueueBufferInput &input, QueueBufferOutput *output) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(slot); + + int64_t timestamp; + bool isAutoTimestamp; + Rect crop; + int scalingMode; + uint32_t transform; + bool async; + sp<Fence> fence; + input.deflate(×tamp, &isAutoTimestamp, &crop, &scalingMode, &transform, + &async, &fence); + + if (fence == NULL) { + BQ_LOGE("queueBuffer: fence is NULL"); + return BAD_VALUE; + } + + switch (scalingMode) { + case NATIVE_WINDOW_SCALING_MODE_FREEZE: + case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: + case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: + case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP: + break; + default: + BQ_LOGE("queueBuffer: unknown scaling mode %d", scalingMode); + return BAD_VALUE; + } + + sp<IConsumerListener> listener; + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("queueBuffer: BufferQueue has been abandoned"); + return NO_INIT; + } + + const int maxBufferCount = mCore->getMaxBufferCountLocked(async); + if (async && mCore->mOverrideMaxBufferCount) { + // FIXME: Some drivers are manually setting the buffer count + // (which they shouldn't), so we do this extra test here to + // handle that case. This is TEMPORARY until we get this fixed. + if (mCore->mOverrideMaxBufferCount < maxBufferCount) { + BQ_LOGE("queueBuffer: async mode is invalid with " + "buffer count override"); + return BAD_VALUE; + } + } + + if (slot < 0 || slot >= maxBufferCount) { + BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)", + slot, maxBufferCount); + return BAD_VALUE; + } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { + BQ_LOGE("queueBuffer: slot %d is not owned by the producer " + "(state = %d)", slot, mSlots[slot].mBufferState); + return BAD_VALUE; + } else if (!mSlots[slot].mRequestBufferCalled) { + BQ_LOGE("queueBuffer: slot %d was queued without requesting " + "a buffer", slot); + return BAD_VALUE; + } + + BQ_LOGV("queueBuffer: slot=%d/%llu time=%llu crop=[%d,%d,%d,%d] " + "transform=%#x scale=%s", + slot, mCore->mFrameCounter + 1, timestamp, + crop.left, crop.top, crop.right, crop.bottom, + transform, BufferItem::scalingModeName(scalingMode)); + + const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer); + Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight()); + Rect croppedRect; + crop.intersect(bufferRect, &croppedRect); + if (croppedRect != crop) { + BQ_LOGE("queueBuffer: crop rect is not contained within the " + "buffer in slot %d", slot); + return BAD_VALUE; + } + + mSlots[slot].mFence = fence; + mSlots[slot].mBufferState = BufferSlot::QUEUED; + ++mCore->mFrameCounter; + mSlots[slot].mFrameNumber = mCore->mFrameCounter; + + BufferItem item; + item.mAcquireCalled = mSlots[slot].mAcquireCalled; + item.mGraphicBuffer = mSlots[slot].mGraphicBuffer; + item.mCrop = crop; + item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; + item.mTransformToDisplayInverse = + bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY); + item.mScalingMode = scalingMode; + item.mTimestamp = timestamp; + item.mIsAutoTimestamp = isAutoTimestamp; + item.mFrameNumber = mCore->mFrameCounter; + item.mSlot = slot; + item.mFence = fence; + item.mIsDroppable = mCore->mDequeueBufferCannotBlock || async; + + if (mCore->mQueue.empty()) { + // When the queue is empty, we can ignore mDequeueBufferCannotBlock + // and simply queue this buffer + mCore->mQueue.push_back(item); + listener = mCore->mConsumerListener; + } else { + // When the queue is not empty, we need to look at the front buffer + // state to see if we need to replace it + BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin()); + if (front->mIsDroppable) { + // If the front queued buffer is still being tracked, we first + // mark it as freed + if (mCore->stillTracking(front)) { + mSlots[front->mSlot].mBufferState = BufferSlot::FREE; + // Reset the frame number of the freed buffer so that it is + // the first in line to be dequeued again + mSlots[front->mSlot].mFrameNumber = 0; + } + // Overwrite the droppable buffer with the incoming one + *front = item; + } else { + mCore->mQueue.push_back(item); + listener = mCore->mConsumerListener; + } + } + + mCore->mBufferHasBeenQueued = true; + mCore->mDequeueCondition.broadcast(); + + output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight, + mCore->mTransformHint, mCore->mQueue.size()); + + ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size()); + } // Autolock scope + + // Call back without lock held + if (listener != NULL) { + listener->onFrameAvailable(); + } + + return NO_ERROR; +} + +void BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) { + ATRACE_CALL(); + BQ_LOGV("cancelBuffer: slot %d", slot); + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + BQ_LOGE("cancelBuffer: BufferQueue has been abandoned"); + return; + } + + if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) { + BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", + slot, BufferQueueDefs::NUM_BUFFER_SLOTS); + return; + } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) { + BQ_LOGE("cancelBuffer: slot %d is not owned by the producer " + "(state = %d)", slot, mSlots[slot].mBufferState); + return; + } else if (fence == NULL) { + BQ_LOGE("cancelBuffer: fence is NULL"); + return; + } + + mSlots[slot].mBufferState = BufferSlot::FREE; + mSlots[slot].mFrameNumber = 0; + mSlots[slot].mFence = fence; + mCore->mDequeueCondition.broadcast(); +} + +int BufferQueueProducer::query(int what, int *outValue) { + ATRACE_CALL(); + Mutex::Autolock lock(mCore->mMutex); + + if (outValue == NULL) { + BQ_LOGE("query: outValue was NULL"); + return BAD_VALUE; + } + + if (mCore->mIsAbandoned) { + BQ_LOGE("query: BufferQueue has been abandoned"); + return NO_INIT; + } + + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mCore->mDefaultWidth; + break; + case NATIVE_WINDOW_HEIGHT: + value = mCore->mDefaultHeight; + break; + case NATIVE_WINDOW_FORMAT: + value = mCore->mDefaultBufferFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = mCore->getMinUndequeuedBufferCountLocked(false); + break; + case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: + value = (mCore->mQueue.size() > 1); + break; + case NATIVE_WINDOW_CONSUMER_USAGE_BITS: + value = mCore->mConsumerUsageBits; + break; + default: + return BAD_VALUE; + } + + BQ_LOGV("query: %d? %d", what, value); + *outValue = value; + return NO_ERROR; +} + +status_t BufferQueueProducer::connect(const sp<android::IBinder> &token, + int api, bool producerControlledByApp, QueueBufferOutput *output) { + ATRACE_CALL(); + Mutex::Autolock lock(mCore->mMutex); + mConsumerName = mCore->mConsumerName; + BQ_LOGV("connect(P): api=%d producerControlledByApp=%s", api, + producerControlledByApp ? "true" : "false"); + + // If we disconnect and reconnect quickly, we can be in a state where our + // slots are empty but we have many buffers in the queue. This can cause us + // to run out of memory if we outrun the consumer. Wait here if it looks + // like we have too many buffers queued up. + while (true) { + if (mCore->mIsAbandoned) { + BQ_LOGE("connect(P): BufferQueue has been abandoned"); + return NO_INIT; + } + + if (mCore->mConsumerListener == NULL) { + BQ_LOGE("connect(P): BufferQueue has no consumer"); + return NO_INIT; + } + + if (output == NULL) { + BQ_LOGE("connect(P): output was NULL"); + return BAD_VALUE; + } + + if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) { + BQ_LOGE("connect(P): already connected (cur=%d req=%d)", + mCore->mConnectedApi, api); + return BAD_VALUE; + } + + size_t maxBufferCount = mCore->getMaxBufferCountLocked(false); + if (mCore->mQueue.size() <= maxBufferCount) { + // The queue size seems small enough to proceed + // TODO: Make this bound tighter? + break; + } + + BQ_LOGV("connect(P): queue size is %d, waiting", mCore->mQueue.size()); + mCore->mDequeueCondition.wait(mCore->mMutex); + } + + int status = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + mCore->mConnectedApi = api; + output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight, + mCore->mTransformHint, mCore->mQueue.size()); + + // Set up a death notification so that we can disconnect + // automatically if the remote producer dies + if (token != NULL && token->remoteBinder() != NULL) { + status = token->linkToDeath( + static_cast<IBinder::DeathRecipient*>(this)); + if (status == NO_ERROR) { + mCore->mConnectedProducerToken = token; + } else { + BQ_LOGE("connect(P): linkToDeath failed: %s (%d)", + strerror(-status), status); + } + } + break; + default: + BQ_LOGE("connect(P): unknown API %d", api); + status = BAD_VALUE; + break; + } + + mCore->mBufferHasBeenQueued = false; + mCore->mDequeueBufferCannotBlock = + mCore->mConsumerControlledByApp && producerControlledByApp; + + return status; +} + +status_t BufferQueueProducer::disconnect(int api) { + ATRACE_CALL(); + BQ_LOGV("disconnect(P): api %d", api); + + int status = NO_ERROR; + sp<IConsumerListener> listener; + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + + if (mCore->mIsAbandoned) { + // It's not really an error to disconnect after the surface has + // been abandoned; it should just be a no-op. + return NO_ERROR; + } + + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mCore->mConnectedApi == api) { + mCore->freeAllBuffersLocked(); + + // Remove our death notification callback if we have one + sp<IBinder> token = mCore->mConnectedProducerToken; + if (token != NULL) { + // This can fail if we're here because of the death + // notification, but we just ignore it + token->unlinkToDeath( + static_cast<IBinder::DeathRecipient*>(this)); + } + mCore->mConnectedProducerToken = NULL; + mCore->mConnectedApi = BufferQueueCore::NO_CONNECTED_API; + mCore->mSidebandStream.clear(); + mCore->mDequeueCondition.broadcast(); + listener = mCore->mConsumerListener; + } else { + BQ_LOGE("disconnect(P): connected to another API " + "(cur=%d req=%d)", mCore->mConnectedApi, api); + status = BAD_VALUE; + } + break; + default: + BQ_LOGE("disconnect(P): unknown API %d", api); + status = BAD_VALUE; + break; + } + } // Autolock scope + + // Call back without lock held + if (listener != NULL) { + listener->onBuffersReleased(); + } + + return status; +} + +status_t BufferQueueProducer::setSidebandStream(const sp<NativeHandle>& stream) { + Mutex::Autolock _l(mCore->mMutex); + mCore->mSidebandStream = stream; + return NO_ERROR; +} + +void BufferQueueProducer::binderDied(const wp<android::IBinder>& /* who */) { + // If we're here, it means that a producer we were connected to died. + // We're guaranteed that we are still connected to it because we remove + // this callback upon disconnect. It's therefore safe to read mConnectedApi + // without synchronization here. + int api = mCore->mConnectedApi; + disconnect(api); +} + +} // namespace android diff --git a/libs/gui/BufferSlot.cpp b/libs/gui/BufferSlot.cpp new file mode 100644 index 0000000..b8877fe --- /dev/null +++ b/libs/gui/BufferSlot.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2014 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 <gui/BufferSlot.h> + +namespace android { + +const char* BufferSlot::bufferStateName(BufferState state) { + switch (state) { + case BufferSlot::DEQUEUED: return "DEQUEUED"; + case BufferSlot::QUEUED: return "QUEUED"; + case BufferSlot::FREE: return "FREE"; + case BufferSlot::ACQUIRED: return "ACQUIRED"; + default: return "Unknown"; + } +} + +} // namespace android diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp index c4ec857..b6adc54 100644 --- a/libs/gui/ConsumerBase.cpp +++ b/libs/gui/ConsumerBase.cpp @@ -85,7 +85,7 @@ ConsumerBase::~ConsumerBase() { "consumer is not abandoned!", mName.string()); } -void ConsumerBase::onLastStrongRef(const void* id) { +void ConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) { abandon(); } @@ -130,6 +130,9 @@ void ConsumerBase::onBuffersReleased() { } } +void ConsumerBase::onSidebandStreamChanged() { +} + void ConsumerBase::abandon() { CB_LOGV("abandon"); Mutex::Autolock lock(mMutex); @@ -243,7 +246,7 @@ status_t ConsumerBase::releaseBufferLocked( slot, mSlots[slot].mFrameNumber); status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, display, eglFence, mSlots[slot].mFence); - if (err == BufferQueue::STALE_BUFFER_SLOT) { + if (err == IGraphicBufferConsumer::STALE_BUFFER_SLOT) { freeBufferLocked(slot); } diff --git a/libs/gui/IConsumerListener.cpp b/libs/gui/IConsumerListener.cpp index 5304462..4ccf0ac 100644 --- a/libs/gui/IConsumerListener.cpp +++ b/libs/gui/IConsumerListener.cpp @@ -28,7 +28,8 @@ namespace android { enum { ON_FRAME_AVAILABLE = IBinder::FIRST_CALL_TRANSACTION, - ON_BUFFER_RELEASED + ON_BUFFER_RELEASED, + ON_SIDEBAND_STREAM_CHANGED, }; class BpConsumerListener : public BpInterface<IConsumerListener> @@ -49,6 +50,12 @@ public: data.writeInterfaceToken(IConsumerListener::getInterfaceDescriptor()); remote()->transact(ON_BUFFER_RELEASED, data, &reply, IBinder::FLAG_ONEWAY); } + + virtual void onSidebandStreamChanged() { + Parcel data, reply; + data.writeInterfaceToken(IConsumerListener::getInterfaceDescriptor()); + remote()->transact(ON_SIDEBAND_STREAM_CHANGED, data, &reply, IBinder::FLAG_ONEWAY); + } }; IMPLEMENT_META_INTERFACE(ConsumerListener, "android.gui.IConsumerListener"); @@ -67,6 +74,10 @@ status_t BnConsumerListener::onTransact( CHECK_INTERFACE(IConsumerListener, data, reply); onBuffersReleased(); return NO_ERROR; + case ON_SIDEBAND_STREAM_CHANGED: + CHECK_INTERFACE(IConsumerListener, data, reply); + onSidebandStreamChanged(); + return NO_ERROR; } return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp index 9574b61..1b19626 100644 --- a/libs/gui/IGraphicBufferConsumer.cpp +++ b/libs/gui/IGraphicBufferConsumer.cpp @@ -24,6 +24,7 @@ #include <sys/types.h> #include <utils/Errors.h> +#include <utils/NativeHandle.h> #include <binder/Parcel.h> #include <binder/IInterface.h> @@ -70,11 +71,11 @@ size_t IGraphicBufferConsumer::BufferItem::getFlattenedSize() const { size_t c = 0; if (mGraphicBuffer != 0) { c += mGraphicBuffer->getFlattenedSize(); - FlattenableUtils::align<4>(c); + c = FlattenableUtils::align<4>(c); } if (mFence != 0) { c += mFence->getFlattenedSize(); - FlattenableUtils::align<4>(c); + c = FlattenableUtils::align<4>(c); } return sizeof(int32_t) + c + getPodSize(); } @@ -90,11 +91,21 @@ size_t IGraphicBufferConsumer::BufferItem::getFdCount() const { return c; } +static void writeBoolAsInt(void*& buffer, size_t& size, bool b) { + FlattenableUtils::write(buffer, size, static_cast<int32_t>(b)); +} + +static bool readBoolFromInt(void const*& buffer, size_t& size) { + int32_t i; + FlattenableUtils::read(buffer, size, i); + return static_cast<bool>(i); +} + status_t IGraphicBufferConsumer::BufferItem::flatten( void*& buffer, size_t& size, int*& fds, size_t& count) const { // make sure we have enough space - if (count < BufferItem::getFlattenedSize()) { + if (size < BufferItem::getFlattenedSize()) { return NO_MEMORY; } @@ -127,12 +138,12 @@ status_t IGraphicBufferConsumer::BufferItem::flatten( FlattenableUtils::write(buffer, size, mTransform); FlattenableUtils::write(buffer, size, mScalingMode); FlattenableUtils::write(buffer, size, mTimestamp); - FlattenableUtils::write(buffer, size, mIsAutoTimestamp); + writeBoolAsInt(buffer, size, mIsAutoTimestamp); FlattenableUtils::write(buffer, size, mFrameNumber); FlattenableUtils::write(buffer, size, mBuf); - FlattenableUtils::write(buffer, size, mIsDroppable); - FlattenableUtils::write(buffer, size, mAcquireCalled); - FlattenableUtils::write(buffer, size, mTransformToDisplayInverse); + writeBoolAsInt(buffer, size, mIsDroppable); + writeBoolAsInt(buffer, size, mAcquireCalled); + writeBoolAsInt(buffer, size, mTransformToDisplayInverse); return NO_ERROR; } @@ -169,12 +180,12 @@ status_t IGraphicBufferConsumer::BufferItem::unflatten( FlattenableUtils::read(buffer, size, mTransform); FlattenableUtils::read(buffer, size, mScalingMode); FlattenableUtils::read(buffer, size, mTimestamp); - FlattenableUtils::read(buffer, size, mIsAutoTimestamp); + mIsAutoTimestamp = readBoolFromInt(buffer, size); FlattenableUtils::read(buffer, size, mFrameNumber); FlattenableUtils::read(buffer, size, mBuf); - FlattenableUtils::read(buffer, size, mIsDroppable); - FlattenableUtils::read(buffer, size, mAcquireCalled); - FlattenableUtils::read(buffer, size, mTransformToDisplayInverse); + mIsDroppable = readBoolFromInt(buffer, size); + mAcquireCalled = readBoolFromInt(buffer, size); + mTransformToDisplayInverse = readBoolFromInt(buffer, size); return NO_ERROR; } @@ -183,6 +194,8 @@ status_t IGraphicBufferConsumer::BufferItem::unflatten( enum { ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION, + DETACH_BUFFER, + ATTACH_BUFFER, RELEASE_BUFFER, CONSUMER_CONNECT, CONSUMER_DISCONNECT, @@ -195,6 +208,7 @@ enum { SET_DEFAULT_BUFFER_FORMAT, SET_CONSUMER_USAGE_BITS, SET_TRANSFORM_HINT, + GET_SIDEBAND_STREAM, DUMP, }; @@ -222,8 +236,33 @@ public: return reply.readInt32(); } + virtual status_t detachBuffer(int slot) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); + data.writeInt32(slot); + status_t result = remote()->transact(DETACH_BUFFER, data, &reply); + if (result != NO_ERROR) { + return result; + } + result = reply.readInt32(); + return result; + } + + virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); + data.write(*buffer.get()); + status_t result = remote()->transact(ATTACH_BUFFER, data, &reply); + if (result != NO_ERROR) { + return result; + } + *slot = reply.readInt32(); + result = reply.readInt32(); + return result; + } + virtual status_t releaseBuffer(int buf, uint64_t frameNumber, - EGLDisplay display, EGLSyncKHR fence, + EGLDisplay display __attribute__((unused)), EGLSyncKHR fence __attribute__((unused)), const sp<Fence>& releaseFence) { Parcel data, reply; data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); @@ -354,6 +393,20 @@ public: return reply.readInt32(); } + virtual sp<NativeHandle> getSidebandStream() const { + Parcel data, reply; + status_t err; + data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); + if ((err = remote()->transact(GET_SIDEBAND_STREAM, data, &reply)) != NO_ERROR) { + return NULL; + } + sp<NativeHandle> stream; + if (reply.readInt32()) { + stream = NativeHandle::create(reply.readNativeHandle(), true); + } + return stream; + } + virtual void dump(String8& result, const char* prefix) const { Parcel data, reply; data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); @@ -382,6 +435,23 @@ status_t BnGraphicBufferConsumer::onTransact( reply->writeInt32(result); return NO_ERROR; } break; + case DETACH_BUFFER: { + CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); + int slot = data.readInt32(); + int result = detachBuffer(slot); + reply->writeInt32(result); + return NO_ERROR; + } break; + case ATTACH_BUFFER: { + CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); + sp<GraphicBuffer> buffer = new GraphicBuffer(); + data.read(*buffer.get()); + int slot; + int result = attachBuffer(&slot, buffer); + reply->writeInt32(slot); + reply->writeInt32(result); + return NO_ERROR; + } break; case RELEASE_BUFFER: { CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); int buf = data.readInt32(); diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp index 0f461e5..7c50315 100644 --- a/libs/gui/IGraphicBufferProducer.cpp +++ b/libs/gui/IGraphicBufferProducer.cpp @@ -18,9 +18,10 @@ #include <sys/types.h> #include <utils/Errors.h> +#include <utils/NativeHandle.h> #include <utils/RefBase.h> -#include <utils/Vector.h> #include <utils/Timers.h> +#include <utils/Vector.h> #include <binder/Parcel.h> #include <binder/IInterface.h> @@ -34,11 +35,14 @@ enum { REQUEST_BUFFER = IBinder::FIRST_CALL_TRANSACTION, SET_BUFFER_COUNT, DEQUEUE_BUFFER, + DETACH_BUFFER, + ATTACH_BUFFER, QUEUE_BUFFER, CANCEL_BUFFER, QUERY, CONNECT, DISCONNECT, + SET_SIDEBAND_STREAM, }; class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer> @@ -106,6 +110,31 @@ public: return result; } + virtual status_t detachBuffer(int slot) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + data.writeInt32(slot); + status_t result = remote()->transact(DETACH_BUFFER, data, &reply); + if (result != NO_ERROR) { + return result; + } + result = reply.readInt32(); + return result; + } + + virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + data.write(*buffer.get()); + status_t result = remote()->transact(ATTACH_BUFFER, data, &reply); + if (result != NO_ERROR) { + return result; + } + *slot = reply.readInt32(); + result = reply.readInt32(); + return result; + } + virtual status_t queueBuffer(int buf, const QueueBufferInput& input, QueueBufferOutput* output) { Parcel data, reply; @@ -169,6 +198,22 @@ public: result = reply.readInt32(); return result; } + + virtual status_t setSidebandStream(const sp<NativeHandle>& stream) { + Parcel data, reply; + status_t result; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + if (stream.get()) { + data.writeInt32(true); + data.writeNativeHandle(stream->handle()); + } else { + data.writeInt32(false); + } + if ((result = remote()->transact(SET_SIDEBAND_STREAM, data, &reply)) == NO_ERROR) { + result = reply.readInt32(); + } + return result; + } }; IMPLEMENT_META_INTERFACE(GraphicBufferProducer, "android.gui.IGraphicBufferProducer"); @@ -216,6 +261,23 @@ status_t BnGraphicBufferProducer::onTransact( reply->writeInt32(result); return NO_ERROR; } break; + case DETACH_BUFFER: { + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + int slot = data.readInt32(); + int result = detachBuffer(slot); + reply->writeInt32(result); + return NO_ERROR; + } break; + case ATTACH_BUFFER: { + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + sp<GraphicBuffer> buffer = new GraphicBuffer(); + data.read(*buffer.get()); + int slot; + int result = attachBuffer(&slot, buffer); + reply->writeInt32(slot); + reply->writeInt32(result); + return NO_ERROR; + } break; case QUEUE_BUFFER: { CHECK_INTERFACE(IGraphicBufferProducer, data, reply); int buf = data.readInt32(); @@ -263,6 +325,16 @@ status_t BnGraphicBufferProducer::onTransact( reply->writeInt32(res); return NO_ERROR; } break; + case SET_SIDEBAND_STREAM: { + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + sp<NativeHandle> stream; + if (data.readInt32()) { + stream = NativeHandle::create(data.readNativeHandle(), true); + } + status_t result = setSidebandStream(stream); + reply->writeInt32(result); + return NO_ERROR; + } break; } return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index aab0604..e96cc54 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -105,7 +105,8 @@ public: virtual status_t captureScreen(const sp<IBinder>& display, const sp<IGraphicBufferProducer>& producer, uint32_t reqWidth, uint32_t reqHeight, - uint32_t minLayerZ, uint32_t maxLayerZ) + uint32_t minLayerZ, uint32_t maxLayerZ, + bool useIdentityTransform) { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); @@ -115,6 +116,7 @@ public: data.writeInt32(reqHeight); data.writeInt32(minLayerZ); data.writeInt32(maxLayerZ); + data.writeInt32(static_cast<int32_t>(useIdentityTransform)); remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply); return reply.readInt32(); } @@ -285,8 +287,11 @@ status_t BnSurfaceComposer::onTransact( uint32_t reqHeight = data.readInt32(); uint32_t minLayerZ = data.readInt32(); uint32_t maxLayerZ = data.readInt32(); + bool useIdentityTransform = static_cast<bool>(data.readInt32()); + status_t res = captureScreen(display, producer, - reqWidth, reqHeight, minLayerZ, maxLayerZ); + reqWidth, reqHeight, minLayerZ, maxLayerZ, + useIdentityTransform); reply->writeInt32(res); return NO_ERROR; } diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 27dbc4e..95f4084 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -86,6 +86,10 @@ sp<IGraphicBufferProducer> Surface::getIGraphicBufferProducer() const { return mGraphicBufferProducer; } +void Surface::setSidebandStream(const sp<NativeHandle>& stream) { + mGraphicBufferProducer->setSidebandStream(stream); +} + int Surface::hook_setSwapInterval(ANativeWindow* window, int interval) { Surface* c = getSelf(window); return c->setSwapInterval(interval); @@ -178,19 +182,38 @@ int Surface::setSwapInterval(int interval) { int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { ATRACE_CALL(); ALOGV("Surface::dequeueBuffer"); - Mutex::Autolock lock(mMutex); + + int reqW; + int reqH; + bool swapIntervalZero; + uint32_t reqFormat; + uint32_t reqUsage; + + { + Mutex::Autolock lock(mMutex); + + reqW = mReqWidth ? mReqWidth : mUserWidth; + reqH = mReqHeight ? mReqHeight : mUserHeight; + + swapIntervalZero = mSwapIntervalZero; + reqFormat = mReqFormat; + reqUsage = mReqUsage; + } // Drop the lock so that we can still touch the Surface while blocking in IGBP::dequeueBuffer + int buf = -1; - int reqW = mReqWidth ? mReqWidth : mUserWidth; - int reqH = mReqHeight ? mReqHeight : mUserHeight; sp<Fence> fence; - status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, mSwapIntervalZero, - reqW, reqH, mReqFormat, mReqUsage); + status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, swapIntervalZero, + reqW, reqH, reqFormat, reqUsage); + if (result < 0) { - ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer(%d, %d, %d, %d)" - "failed: %d", mReqWidth, mReqHeight, mReqFormat, mReqUsage, + ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer(%d, %d, %d, %d, %d)" + "failed: %d", swapIntervalZero, reqW, reqH, reqFormat, reqUsage, result); return result; } + + Mutex::Autolock lock(mMutex); + sp<GraphicBuffer>& gbuf(mSlots[buf].buffer); // this should never happen @@ -251,7 +274,7 @@ int Surface::getSlotFromBufferLocked( return BAD_VALUE; } -int Surface::lockBuffer_DEPRECATED(android_native_buffer_t* buffer) { +int Surface::lockBuffer_DEPRECATED(android_native_buffer_t* buffer __attribute__((unused))) { ALOGV("Surface::lockBuffer"); Mutex::Autolock lock(mMutex); return OK; @@ -482,7 +505,7 @@ int Surface::dispatchLock(va_list args) { return lock(outBuffer, inOutDirtyBounds); } -int Surface::dispatchUnlockAndPost(va_list args) { +int Surface::dispatchUnlockAndPost(va_list args __attribute__((unused))) { return unlockAndPost(); } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 2246f5f..6b20eaf 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -628,11 +628,11 @@ status_t ScreenshotClient::capture( const sp<IBinder>& display, const sp<IGraphicBufferProducer>& producer, uint32_t reqWidth, uint32_t reqHeight, - uint32_t minLayerZ, uint32_t maxLayerZ) { + uint32_t minLayerZ, uint32_t maxLayerZ, bool useIdentityTransform) { sp<ISurfaceComposer> s(ComposerService::getComposerService()); if (s == NULL) return NO_INIT; return s->captureScreen(display, producer, - reqWidth, reqHeight, minLayerZ, maxLayerZ); + reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform); } ScreenshotClient::ScreenshotClient() @@ -646,8 +646,9 @@ ScreenshotClient::~ScreenshotClient() { sp<CpuConsumer> ScreenshotClient::getCpuConsumer() const { if (mCpuConsumer == NULL) { - mBufferQueue = new BufferQueue(); - mCpuConsumer = new CpuConsumer(mBufferQueue, 1); + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&mProducer, &consumer); + mCpuConsumer = new CpuConsumer(consumer, 1); mCpuConsumer->setName(String8("ScreenshotClient")); } return mCpuConsumer; @@ -655,7 +656,8 @@ sp<CpuConsumer> ScreenshotClient::getCpuConsumer() const { status_t ScreenshotClient::update(const sp<IBinder>& display, uint32_t reqWidth, uint32_t reqHeight, - uint32_t minLayerZ, uint32_t maxLayerZ) { + uint32_t minLayerZ, uint32_t maxLayerZ, + bool useIdentityTransform) { sp<ISurfaceComposer> s(ComposerService::getComposerService()); if (s == NULL) return NO_INIT; sp<CpuConsumer> cpuConsumer = getCpuConsumer(); @@ -666,8 +668,8 @@ status_t ScreenshotClient::update(const sp<IBinder>& display, mHaveBuffer = false; } - status_t err = s->captureScreen(display, mBufferQueue, - reqWidth, reqHeight, minLayerZ, maxLayerZ); + status_t err = s->captureScreen(display, mProducer, + reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform); if (err == NO_ERROR) { err = mCpuConsumer->lockNextBuffer(&mBuffer); @@ -678,13 +680,16 @@ status_t ScreenshotClient::update(const sp<IBinder>& display, return err; } -status_t ScreenshotClient::update(const sp<IBinder>& display) { - return ScreenshotClient::update(display, 0, 0, 0, -1UL); +status_t ScreenshotClient::update(const sp<IBinder>& display, + bool useIdentityTransform) { + return ScreenshotClient::update(display, 0, 0, 0, -1UL, + useIdentityTransform); } status_t ScreenshotClient::update(const sp<IBinder>& display, - uint32_t reqWidth, uint32_t reqHeight) { - return ScreenshotClient::update(display, reqWidth, reqHeight, 0, -1UL); + uint32_t reqWidth, uint32_t reqHeight, bool useIdentityTransform) { + return ScreenshotClient::update(display, reqWidth, reqHeight, 0, -1UL, + useIdentityTransform); } void ScreenshotClient::release() { diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 16e533c..de182ee 100644 --- a/libs/gui/SurfaceControl.cpp +++ b/libs/gui/SurfaceControl.cpp @@ -23,7 +23,6 @@ #include <android/native_window.h> -#include <utils/CallStack.h> #include <utils/Errors.h> #include <utils/Log.h> #include <utils/threads.h> diff --git a/libs/gui/tests/Android.mk b/libs/gui/tests/Android.mk index 21bd875..2eeb5c7 100644 --- a/libs/gui/tests/Android.mk +++ b/libs/gui/tests/Android.mk @@ -9,9 +9,19 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ BufferQueue_test.cpp \ CpuConsumer_test.cpp \ + FillBuffer.cpp \ + GLTest.cpp \ + IGraphicBufferProducer_test.cpp \ + MultiTextureConsumer_test.cpp \ + SRGB_test.cpp \ SurfaceTextureClient_test.cpp \ - SurfaceTexture_test.cpp \ + SurfaceTextureFBO_test.cpp \ + SurfaceTextureGLThreadToGL_test.cpp \ + SurfaceTextureGLToGL_test.cpp \ + SurfaceTextureGL_test.cpp \ + SurfaceTextureMultiContextGL_test.cpp \ Surface_test.cpp \ + TextureRenderer.cpp \ LOCAL_SHARED_LIBRARIES := \ libEGL \ diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index 03c1a29..7943476 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -23,49 +23,126 @@ #include <utils/threads.h> #include <ui/GraphicBuffer.h> -#include <ui/FramebufferNativeWindow.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> #include <gui/BufferQueue.h> namespace android { class BufferQueueTest : public ::testing::Test { -protected: - - BufferQueueTest() {} - virtual void SetUp() { +public: +protected: + BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); - - mBQ = new BufferQueue(); } - virtual void TearDown() { - mBQ.clear(); - + ~BufferQueueTest() { const ::testing::TestInfo* const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); } - sp<BufferQueue> mBQ; + void GetMinUndequeuedBufferCount(int* bufferCount) { + ASSERT_TRUE(bufferCount != NULL); + ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, + bufferCount)); + ASSERT_GE(*bufferCount, 0); + } + + void createBufferQueue() { + BufferQueue::createBufferQueue(&mProducer, &mConsumer); + } + + sp<IGraphicBufferProducer> mProducer; + sp<IGraphicBufferConsumer> mConsumer; }; struct DummyConsumer : public BnConsumerListener { virtual void onFrameAvailable() {} virtual void onBuffersReleased() {} + virtual void onSidebandStreamChanged() {} }; +// XXX: Tests that fork a process to hold the BufferQueue must run before tests +// that use a local BufferQueue, or else Binder will get unhappy +TEST_F(BufferQueueTest, BufferQueueInAnotherProcess) { + const String16 PRODUCER_NAME = String16("BQTestProducer"); + const String16 CONSUMER_NAME = String16("BQTestConsumer"); + + pid_t forkPid = fork(); + ASSERT_NE(forkPid, -1); + + if (forkPid == 0) { + // Child process + sp<BnGraphicBufferProducer> producer; + sp<BnGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp<IServiceManager> serviceManager = defaultServiceManager(); + serviceManager->addService(PRODUCER_NAME, producer.get()); + serviceManager->addService(CONSUMER_NAME, consumer.get()); + ProcessState::self()->startThreadPool(); + IPCThreadState::self()->joinThreadPool(); + LOG_ALWAYS_FATAL("Shouldn't be here"); + } + + sp<IServiceManager> serviceManager = defaultServiceManager(); + sp<IBinder> binderProducer = + serviceManager->getService(PRODUCER_NAME); + mProducer = interface_cast<IGraphicBufferProducer>(binderProducer); + EXPECT_TRUE(mProducer != NULL); + sp<IBinder> binderConsumer = + serviceManager->getService(CONSUMER_NAME); + mConsumer = interface_cast<IGraphicBufferConsumer>(binderConsumer); + EXPECT_TRUE(mConsumer != NULL); + + sp<DummyConsumer> dc(new DummyConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false)); + IGraphicBufferProducer::QueueBufferOutput output; + ASSERT_EQ(OK, + mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + + uint32_t* dataIn; + ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, + reinterpret_cast<void**>(&dataIn))); + *dataIn = 0x12345678; + ASSERT_EQ(OK, buffer->unlock()); + + IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); + + uint32_t* dataOut; + ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, + reinterpret_cast<void**>(&dataOut))); + ASSERT_EQ(*dataOut, 0x12345678); + ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); +} + TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) { + createBufferQueue(); sp<DummyConsumer> dc(new DummyConsumer); - mBQ->consumerConnect(dc, false); + mConsumer->consumerConnect(dc, false); IGraphicBufferProducer::QueueBufferOutput qbo; - mBQ->connect(NULL, NATIVE_WINDOW_API_CPU, false, &qbo); - mBQ->setBufferCount(4); + mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &qbo); + mProducer->setBufferCount(4); int slot; sp<Fence> fence; @@ -76,42 +153,206 @@ TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) { for (int i = 0; i < 2; i++) { ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, - mBQ->dequeueBuffer(&slot, &fence, false, 1, 1, 0, + mProducer->dequeueBuffer(&slot, &fence, false, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN)); - ASSERT_EQ(OK, mBQ->requestBuffer(slot, &buf)); - ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo)); - ASSERT_EQ(OK, mBQ->acquireBuffer(&item, 0)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buf)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0)); } ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, - mBQ->dequeueBuffer(&slot, &fence, false, 1, 1, 0, + mProducer->dequeueBuffer(&slot, &fence, false, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN)); - ASSERT_EQ(OK, mBQ->requestBuffer(slot, &buf)); - ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buf)); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo)); // Acquire the third buffer, which should fail. - ASSERT_EQ(INVALID_OPERATION, mBQ->acquireBuffer(&item, 0)); + ASSERT_EQ(INVALID_OPERATION, mConsumer->acquireBuffer(&item, 0)); } TEST_F(BufferQueueTest, SetMaxAcquiredBufferCountWithIllegalValues_ReturnsError) { + createBufferQueue(); sp<DummyConsumer> dc(new DummyConsumer); - mBQ->consumerConnect(dc, false); + mConsumer->consumerConnect(dc, false); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(0)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(-3)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount( + int minBufferCount; + ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount)); + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount( + minBufferCount - 1)); + + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(0)); + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(-3)); + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount( BufferQueue::MAX_MAX_ACQUIRED_BUFFERS+1)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(100)); + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(100)); } TEST_F(BufferQueueTest, SetMaxAcquiredBufferCountWithLegalValues_Succeeds) { + createBufferQueue(); sp<DummyConsumer> dc(new DummyConsumer); - mBQ->consumerConnect(dc, false); + mConsumer->consumerConnect(dc, false); + + int minBufferCount; + ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount)); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(1)); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(2)); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount( + EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1)); + EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(2)); + EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(minBufferCount)); + EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount( BufferQueue::MAX_MAX_ACQUIRED_BUFFERS)); } +TEST_F(BufferQueueTest, DetachAndReattachOnProducerSide) { + createBufferQueue(); + sp<DummyConsumer> dc(new DummyConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false)); + IGraphicBufferProducer::QueueBufferOutput output; + ASSERT_EQ(OK, + mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output)); + + ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(-1)); // Index too low + ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer( + BufferQueueDefs::NUM_BUFFER_SLOTS)); // Index too high + ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(0)); // Not dequeued + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(slot)); // Not requested + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + ASSERT_EQ(OK, mProducer->detachBuffer(slot)); + ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(slot)); // Not dequeued + + sp<GraphicBuffer> safeToClobberBuffer; + // Can no longer request buffer from this slot + ASSERT_EQ(BAD_VALUE, mProducer->requestBuffer(slot, &safeToClobberBuffer)); + + uint32_t* dataIn; + ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, + reinterpret_cast<void**>(&dataIn))); + *dataIn = 0x12345678; + ASSERT_EQ(OK, buffer->unlock()); + + int newSlot; + ASSERT_EQ(BAD_VALUE, mProducer->attachBuffer(NULL, safeToClobberBuffer)); + ASSERT_EQ(BAD_VALUE, mProducer->attachBuffer(&newSlot, NULL)); + + ASSERT_EQ(OK, mProducer->attachBuffer(&newSlot, buffer)); + IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE); + ASSERT_EQ(OK, mProducer->queueBuffer(newSlot, input, &output)); + + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0))); + + uint32_t* dataOut; + ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, + reinterpret_cast<void**>(&dataOut))); + ASSERT_EQ(*dataOut, 0x12345678); + ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); +} + +TEST_F(BufferQueueTest, DetachAndReattachOnConsumerSide) { + createBufferQueue(); + sp<DummyConsumer> dc(new DummyConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false)); + IGraphicBufferProducer::QueueBufferOutput output; + ASSERT_EQ(OK, + mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + + ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(-1)); // Index too low + ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer( + BufferQueueDefs::NUM_BUFFER_SLOTS)); // Index too high + ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(0)); // Not acquired + + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0))); + + ASSERT_EQ(OK, mConsumer->detachBuffer(item.mBuf)); + ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(item.mBuf)); // Not acquired + + uint32_t* dataIn; + ASSERT_EQ(OK, item.mGraphicBuffer->lock( + GraphicBuffer::USAGE_SW_WRITE_OFTEN, + reinterpret_cast<void**>(&dataIn))); + *dataIn = 0x12345678; + ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); + + int newSlot; + sp<GraphicBuffer> safeToClobberBuffer; + ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(NULL, safeToClobberBuffer)); + ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, NULL)); + ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer)); + + ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mBuf, 0, EGL_NO_DISPLAY, + EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + + uint32_t* dataOut; + ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, + reinterpret_cast<void**>(&dataOut))); + ASSERT_EQ(*dataOut, 0x12345678); + ASSERT_EQ(OK, buffer->unlock()); +} + +TEST_F(BufferQueueTest, MoveFromConsumerToProducer) { + createBufferQueue(); + sp<DummyConsumer> dc(new DummyConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false)); + IGraphicBufferProducer::QueueBufferOutput output; + ASSERT_EQ(OK, + mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + + uint32_t* dataIn; + ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, + reinterpret_cast<void**>(&dataIn))); + *dataIn = 0x12345678; + ASSERT_EQ(OK, buffer->unlock()); + + IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1), + NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE); + ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output)); + + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0))); + ASSERT_EQ(OK, mConsumer->detachBuffer(item.mBuf)); + + int newSlot; + ASSERT_EQ(OK, mProducer->attachBuffer(&newSlot, item.mGraphicBuffer)); + ASSERT_EQ(OK, mProducer->queueBuffer(newSlot, input, &output)); + ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0))); + + uint32_t* dataOut; + ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, + reinterpret_cast<void**>(&dataOut))); + ASSERT_EQ(*dataOut, 0x12345678); + ASSERT_EQ(OK, item.mGraphicBuffer->unlock()); +} + } // namespace android diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp index b370a2d..9f61a09 100644 --- a/libs/gui/tests/CpuConsumer_test.cpp +++ b/libs/gui/tests/CpuConsumer_test.cpp @@ -33,8 +33,6 @@ #include <utils/Mutex.h> #include <utils/Condition.h> -#include <ui/FramebufferNativeWindow.h> - #define CPU_CONSUMER_TEST_FORMAT_RAW 0 #define CPU_CONSUMER_TEST_FORMAT_Y8 0 #define CPU_CONSUMER_TEST_FORMAT_Y16 0 diff --git a/libs/gui/tests/DisconnectWaiter.h b/libs/gui/tests/DisconnectWaiter.h new file mode 100644 index 0000000..56e96c2 --- /dev/null +++ b/libs/gui/tests/DisconnectWaiter.h @@ -0,0 +1,80 @@ +/* + * 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_DISCONNECT_WAITER_H +#define ANDROID_DISCONNECT_WAITER_H + +#include <gui/IConsumerListener.h> + +#include <utils/Condition.h> +#include <utils/Mutex.h> + +namespace android { + +// Note that GLConsumer will lose the notifications +// onBuffersReleased and onFrameAvailable as there is currently +// no way to forward the events. This DisconnectWaiter will not let the +// disconnect finish until finishDisconnect() is called. It will +// also block until a disconnect is called +class DisconnectWaiter : public BnConsumerListener { +public: + DisconnectWaiter () : + mWaitForDisconnect(false), + mPendingFrames(0) { + } + + void waitForFrame() { + Mutex::Autolock lock(mMutex); + while (mPendingFrames == 0) { + mFrameCondition.wait(mMutex); + } + mPendingFrames--; + } + + virtual void onFrameAvailable() { + Mutex::Autolock lock(mMutex); + mPendingFrames++; + mFrameCondition.signal(); + } + + virtual void onBuffersReleased() { + Mutex::Autolock lock(mMutex); + while (!mWaitForDisconnect) { + mDisconnectCondition.wait(mMutex); + } + } + + virtual void onSidebandStreamChanged() {} + + void finishDisconnect() { + Mutex::Autolock lock(mMutex); + mWaitForDisconnect = true; + mDisconnectCondition.signal(); + } + +private: + Mutex mMutex; + + bool mWaitForDisconnect; + Condition mDisconnectCondition; + + int mPendingFrames; + Condition mFrameCondition; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/FillBuffer.cpp b/libs/gui/tests/FillBuffer.cpp new file mode 100644 index 0000000..079962c --- /dev/null +++ b/libs/gui/tests/FillBuffer.cpp @@ -0,0 +1,108 @@ +/* + * 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. + */ + +#include "FillBuffer.h" + +#include <ui/GraphicBuffer.h> + +#include <gtest/gtest.h> + +namespace android { + +void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) { + const int blockWidth = w > 16 ? w / 16 : 1; + const int blockHeight = h > 16 ? h / 16 : 1; + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + int parityX = (x / blockWidth) & 1; + int parityY = (y / blockHeight) & 1; + unsigned char intensity = (parityX ^ parityY) ? 63 : 191; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity; + if (x < w / 2 && y < h / 2) { + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity; + if (x * 2 < w / 2 && y * 2 < h / 2) { + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] = + intensity; + } + } + } + } +} + +void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride, + const android_native_rect_t& rect) { + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + bool inside = rect.left <= x && x < rect.right && + rect.top <= y && y < rect.bottom; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64; + if (x < w / 2 && y < h / 2) { + bool inside = rect.left <= 2*x && 2*x < rect.right && + rect.top <= 2*y && 2*y < rect.bottom; + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16; + buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] = + inside ? 16 : 255; + } + } + } +} + +void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) { + const size_t PIXEL_SIZE = 4; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + off_t offset = (y * stride + x) * PIXEL_SIZE; + for (int c = 0; c < 4; c++) { + int parityX = (x / (1 << (c+2))) & 1; + int parityY = (y / (1 << (c+2))) & 1; + buf[offset + c] = (parityX ^ parityY) ? 231 : 35; + } + } + } +} + +void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) { + android_native_buffer_t* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + uint8_t* img = NULL; + ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, + (void**)(&img))); + fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride()); + ASSERT_EQ(NO_ERROR, buf->unlock()); + ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer(), + -1)); +} +} // namespace android diff --git a/libs/gui/tests/FillBuffer.h b/libs/gui/tests/FillBuffer.h new file mode 100644 index 0000000..b584179 --- /dev/null +++ b/libs/gui/tests/FillBuffer.h @@ -0,0 +1,43 @@ +/* + * 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_FILL_BUFFER_H +#define ANDROID_FILL_BUFFER_H + +#include <system/window.h> +#include <utils/StrongPointer.h> + +namespace android { + +// Fill a YV12 buffer with a multi-colored checkerboard pattern +void fillYV12Buffer(uint8_t* buf, int w, int h, int stride); + +// Fill a YV12 buffer with red outside a given rectangle and green inside it. +void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride, + const android_native_rect_t& rect); + +void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride); + +// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern +// using the CPU. This assumes that the ANativeWindow is already configured to +// allow this to be done (e.g. the format is set to RGBA8). +// +// Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE(). +void produceOneRGBA8Frame(const sp<ANativeWindow>& anw); + +} // namespace android + +#endif diff --git a/libs/gui/tests/FrameWaiter.h b/libs/gui/tests/FrameWaiter.h new file mode 100644 index 0000000..bdedba6 --- /dev/null +++ b/libs/gui/tests/FrameWaiter.h @@ -0,0 +1,52 @@ +/* + * 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_FRAME_WAITER_H +#define ANDROID_FRAME_WAITER_H + +#include <gui/GLConsumer.h> + +namespace android { + +class FrameWaiter : public GLConsumer::FrameAvailableListener { +public: + FrameWaiter(): + mPendingFrames(0) { + } + + void waitForFrame() { + Mutex::Autolock lock(mMutex); + while (mPendingFrames == 0) { + mCondition.wait(mMutex); + } + mPendingFrames--; + } + + virtual void onFrameAvailable() { + Mutex::Autolock lock(mMutex); + mPendingFrames++; + mCondition.signal(); + } + +private: + int mPendingFrames; + Mutex mMutex; + Condition mCondition; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp new file mode 100644 index 0000000..1739d9c --- /dev/null +++ b/libs/gui/tests/GLTest.cpp @@ -0,0 +1,339 @@ +/* + * 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. + */ + +#include "GLTest.h" + +#include <gui/Surface.h> + +#include <GLES2/gl2.h> + +namespace android { + +static int abs(int value) { + return value > 0 ? value : -value; +} + +void GLTest::SetUp() { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); + + mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); + + EGLint majorVersion; + EGLint minorVersion; + EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + RecordProperty("EglVersionMajor", majorVersion); + RecordProperty("EglVersionMinor", minorVersion); + + EGLint numConfigs = 0; + EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig, 1, + &numConfigs)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS"); + if (displaySecsEnv != NULL) { + mDisplaySecs = atoi(displaySecsEnv); + if (mDisplaySecs < 0) { + mDisplaySecs = 0; + } + } else { + mDisplaySecs = 0; + } + + if (mDisplaySecs > 0) { + mComposerClient = new SurfaceComposerClient; + ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); + + mSurfaceControl = mComposerClient->createSurface( + String8("Test Surface"), getSurfaceWidth(), getSurfaceHeight(), + PIXEL_FORMAT_RGB_888, 0); + + ASSERT_TRUE(mSurfaceControl != NULL); + ASSERT_TRUE(mSurfaceControl->isValid()); + + SurfaceComposerClient::openGlobalTransaction(); + ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF)); + ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); + SurfaceComposerClient::closeGlobalTransaction(); + + sp<ANativeWindow> window = mSurfaceControl->getSurface(); + mEglSurface = createWindowSurface(mEglDisplay, mGlConfig, window); + } else { + EGLint pbufferAttribs[] = { + EGL_WIDTH, getSurfaceWidth(), + EGL_HEIGHT, getSurfaceHeight(), + EGL_NONE }; + + mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig, + pbufferAttribs); + } + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface); + + mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, + getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mEglContext); + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + EGLint w, h; + EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + RecordProperty("EglSurfaceWidth", w); + RecordProperty("EglSurfaceHeight", h); + + glViewport(0, 0, w, h); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); +} + +void GLTest::TearDown() { + // Display the result + if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) { + eglSwapBuffers(mEglDisplay, mEglSurface); + sleep(mDisplaySecs); + } + + if (mComposerClient != NULL) { + mComposerClient->dispose(); + } + if (mEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mEglContext); + } + if (mEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mEglSurface); + } + if (mEglDisplay != EGL_NO_DISPLAY) { + eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglTerminate(mEglDisplay); + } + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name()); +} + +EGLint const* GLTest::getConfigAttribs() { + static const EGLint sDefaultConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 8, + EGL_NONE }; + + return sDefaultConfigAttribs; +} + +EGLint const* GLTest::getContextAttribs() { + static const EGLint sDefaultContextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE }; + + return sDefaultContextAttribs; +} + +EGLint GLTest::getSurfaceWidth() { + return 512; +} + +EGLint GLTest::getSurfaceHeight() { + return 512; +} + +EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config, + sp<ANativeWindow>& window) const { + return eglCreateWindowSurface(display, config, window.get(), NULL); +} + +::testing::AssertionResult GLTest::checkPixel(int x, int y, + int r, int g, int b, int a, int tolerance) { + GLubyte pixel[4]; + String8 msg; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + msg += String8::format("error reading pixel: %#x", err); + while ((err = glGetError()) != GL_NO_ERROR) { + msg += String8::format(", %#x", err); + } + return ::testing::AssertionFailure(::testing::Message(msg.string())); + } + if (r >= 0 && abs(r - int(pixel[0])) > tolerance) { + msg += String8::format("r(%d isn't %d)", pixel[0], r); + } + if (g >= 0 && abs(g - int(pixel[1])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("g(%d isn't %d)", pixel[1], g); + } + if (b >= 0 && abs(b - int(pixel[2])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("b(%d isn't %d)", pixel[2], b); + } + if (a >= 0 && abs(a - int(pixel[3])) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("a(%d isn't %d)", pixel[3], a); + } + if (!msg.isEmpty()) { + return ::testing::AssertionFailure(::testing::Message(msg.string())); + } else { + return ::testing::AssertionSuccess(); + } +} + +::testing::AssertionResult GLTest::assertRectEq(const Rect &r1, const Rect &r2, + int tolerance) { + String8 msg; + + if (abs(r1.left - r2.left) > tolerance) { + msg += String8::format("left(%d isn't %d)", r1.left, r2.left); + } + if (abs(r1.top - r2.top) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("top(%d isn't %d)", r1.top, r2.top); + } + if (abs(r1.right - r2.right) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("right(%d isn't %d)", r1.right, r2.right); + } + if (abs(r1.bottom - r2.bottom) > tolerance) { + if (!msg.isEmpty()) { + msg += " "; + } + msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom); + } + if (!msg.isEmpty()) { + msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]", + r1.left, r1.top, r1.right, r1.bottom, + r2.left, r2.top, r2.right, r2.bottom); + fprintf(stderr, "assertRectEq: %s\n", msg.string()); + return ::testing::AssertionFailure(::testing::Message(msg.string())); + } else { + return ::testing::AssertionSuccess(); + } +} + +void GLTest::loadShader(GLenum shaderType, const char* pSource, + GLuint* outShader) { + GLuint shader = glCreateShader(shaderType); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (shader) { + glShaderSource(shader, 1, &pSource, NULL); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glCompileShader(shader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (infoLen) { + char* buf = (char*) malloc(infoLen); + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + printf("Shader compile log:\n%s\n", buf); + free(buf); + FAIL(); + } + } else { + char* buf = (char*) malloc(0x1000); + if (buf) { + glGetShaderInfoLog(shader, 0x1000, NULL, buf); + printf("Shader compile log:\n%s\n", buf); + free(buf); + FAIL(); + } + } + glDeleteShader(shader); + shader = 0; + } + } + ASSERT_TRUE(shader != 0); + *outShader = shader; +} + +void GLTest::createProgram(const char* pVertexSource, + const char* pFragmentSource, GLuint* outPgm) { + GLuint vertexShader, fragmentShader; + { + SCOPED_TRACE("compiling vertex shader"); + ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource, + &vertexShader)); + } + { + SCOPED_TRACE("compiling fragment shader"); + ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource, + &fragmentShader)); + } + + GLuint program = glCreateProgram(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + if (program) { + glAttachShader(program, vertexShader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glAttachShader(program, fragmentShader); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char* buf = (char*) malloc(bufLength); + if (buf) { + glGetProgramInfoLog(program, bufLength, NULL, buf); + printf("Program link log:\n%s\n", buf); + free(buf); + FAIL(); + } + } + glDeleteProgram(program); + program = 0; + } + } + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + ASSERT_TRUE(program != 0); + *outPgm = program; +} + +} // namespace android diff --git a/libs/gui/tests/GLTest.h b/libs/gui/tests/GLTest.h new file mode 100644 index 0000000..d3c4a95 --- /dev/null +++ b/libs/gui/tests/GLTest.h @@ -0,0 +1,70 @@ +/* + * 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_GL_TEST_H +#define ANDROID_GL_TEST_H + +#include <gtest/gtest.h> + +#include <gui/SurfaceComposerClient.h> + +#include <EGL/egl.h> +#include <GLES/gl.h> + +namespace android { + +class GLTest : public ::testing::Test { +public: + static void loadShader(GLenum shaderType, const char* pSource, + GLuint* outShader); + static void createProgram(const char* pVertexSource, + const char* pFragmentSource, GLuint* outPgm); + +protected: + GLTest() : + mEglDisplay(EGL_NO_DISPLAY), + mEglSurface(EGL_NO_SURFACE), + mEglContext(EGL_NO_CONTEXT) { + } + + virtual void SetUp(); + virtual void TearDown(); + + virtual EGLint const* getConfigAttribs(); + virtual EGLint const* getContextAttribs(); + virtual EGLint getSurfaceWidth(); + virtual EGLint getSurfaceHeight(); + virtual EGLSurface createWindowSurface(EGLDisplay display, EGLConfig config, + sp<ANativeWindow>& window) const; + + ::testing::AssertionResult checkPixel(int x, int y, + int r, int g, int b, int a, int tolerance = 2); + ::testing::AssertionResult assertRectEq(const Rect &r1, const Rect &r2, + int tolerance = 1); + + int mDisplaySecs; + sp<SurfaceComposerClient> mComposerClient; + sp<SurfaceControl> mSurfaceControl; + + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLContext mEglContext; + EGLConfig mGlConfig; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp new file mode 100644 index 0000000..c2653c2 --- /dev/null +++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp @@ -0,0 +1,568 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "IGraphicBufferProducer_test" +//#define LOG_NDEBUG 0 + +#include <gtest/gtest.h> + +#include <utils/String8.h> +#include <utils/threads.h> + +#include <ui/GraphicBuffer.h> + +#include <gui/BufferQueue.h> + +#include <vector> + +#define ASSERT_OK(x) ASSERT_EQ(OK, (x)) +#define EXPECT_OK(x) EXPECT_EQ(OK, (x)) + +#define TEST_TOKEN ((IBinder*)(NULL)) +#define TEST_API NATIVE_WINDOW_API_CPU +#define TEST_API_OTHER NATIVE_WINDOW_API_EGL // valid API that's not TEST_API +#define TEST_CONTROLLED_BY_APP false +#define TEST_PRODUCER_USAGE_BITS (0) + +// TODO: Make these public constants in a header +enum { + // Default dimensions before setDefaultBufferSize is called + DEFAULT_WIDTH = 1, + DEFAULT_HEIGHT = 1, + + // Default format before setDefaultBufferFormat is called + DEFAULT_FORMAT = HAL_PIXEL_FORMAT_RGBA_8888, + + // Default transform hint before setTransformHint is called + DEFAULT_TRANSFORM_HINT = 0, +}; + +namespace android { + +namespace { +// Parameters for a generic "valid" input for queueBuffer. +const int64_t QUEUE_BUFFER_INPUT_TIMESTAMP = 1384888611; +const bool QUEUE_BUFFER_INPUT_IS_AUTO_TIMESTAMP = false; +const Rect QUEUE_BUFFER_INPUT_RECT = Rect(DEFAULT_WIDTH, DEFAULT_HEIGHT); +const int QUEUE_BUFFER_INPUT_SCALING_MODE = 0; +const int QUEUE_BUFFER_INPUT_TRANSFORM = 0; +const bool QUEUE_BUFFER_INPUT_ASYNC = false; +const sp<Fence> QUEUE_BUFFER_INPUT_FENCE = Fence::NO_FENCE; +}; // namespace anonymous + +struct DummyConsumer : public BnConsumerListener { + virtual void onFrameAvailable() {} + virtual void onBuffersReleased() {} + virtual void onSidebandStreamChanged() {} +}; + +class IGraphicBufferProducerTest : public ::testing::Test { +protected: + + IGraphicBufferProducerTest() {} + + virtual void SetUp() { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + testInfo->name()); + + mBQ = new BufferQueue(); + mDC = new DummyConsumer; + + mProducer = mBQ; + mConsumer = mBQ; + + // Test check: Can't connect producer if no consumer yet + ASSERT_EQ(NO_INIT, TryConnectProducer()); + + // Must connect consumer before producer connects will succeed. + ASSERT_OK(mConsumer->consumerConnect(mDC, /*controlledByApp*/false)); + } + + virtual void TearDown() { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("End test: %s.%s", testInfo->test_case_name(), + testInfo->name()); + } + + status_t TryConnectProducer() { + IGraphicBufferProducer::QueueBufferOutput output; + return mProducer->connect(TEST_TOKEN, + TEST_API, + TEST_CONTROLLED_BY_APP, + &output); + // TODO: use params to vary token, api, producercontrolledbyapp, etc + } + + // Connect to a producer in a 'correct' fashion. + // Precondition: Consumer is connected. + void ConnectProducer() { + ASSERT_OK(TryConnectProducer()); + } + + // Create a generic "valid" input for queueBuffer + // -- uses the default buffer format, width, etc. + static IGraphicBufferProducer::QueueBufferInput CreateBufferInput() { + return QueueBufferInputBuilder().build(); + } + + // Builder pattern to slightly vary *almost* correct input + // -- avoids copying and pasting + struct QueueBufferInputBuilder { + QueueBufferInputBuilder() { + timestamp = QUEUE_BUFFER_INPUT_TIMESTAMP; + isAutoTimestamp = QUEUE_BUFFER_INPUT_IS_AUTO_TIMESTAMP; + crop = QUEUE_BUFFER_INPUT_RECT; + scalingMode = QUEUE_BUFFER_INPUT_SCALING_MODE; + transform = QUEUE_BUFFER_INPUT_TRANSFORM; + async = QUEUE_BUFFER_INPUT_ASYNC; + fence = QUEUE_BUFFER_INPUT_FENCE; + } + + IGraphicBufferProducer::QueueBufferInput build() { + return IGraphicBufferProducer::QueueBufferInput( + timestamp, + isAutoTimestamp, + crop, + scalingMode, + transform, + async, + fence); + } + + QueueBufferInputBuilder& setTimestamp(int64_t timestamp) { + this->timestamp = timestamp; + return *this; + } + + QueueBufferInputBuilder& setIsAutoTimestamp(bool isAutoTimestamp) { + this->isAutoTimestamp = isAutoTimestamp; + return *this; + } + + QueueBufferInputBuilder& setCrop(Rect crop) { + this->crop = crop; + return *this; + } + + QueueBufferInputBuilder& setScalingMode(int scalingMode) { + this->scalingMode = scalingMode; + return *this; + } + + QueueBufferInputBuilder& setTransform(uint32_t transform) { + this->transform = transform; + return *this; + } + + QueueBufferInputBuilder& setAsync(bool async) { + this->async = async; + return *this; + } + + QueueBufferInputBuilder& setFence(sp<Fence> fence) { + this->fence = fence; + return *this; + } + + private: + int64_t timestamp; + bool isAutoTimestamp; + Rect crop; + int scalingMode; + uint32_t transform; + int async; + sp<Fence> fence; + }; // struct QueueBufferInputBuilder + + // To easily store dequeueBuffer results into containers + struct DequeueBufferResult { + int slot; + sp<Fence> fence; + }; + + status_t dequeueBuffer(bool async, uint32_t w, uint32_t h, uint32_t format, uint32_t usage, DequeueBufferResult* result) { + return mProducer->dequeueBuffer(&result->slot, &result->fence, async, w, h, format, usage); + } + +private: // hide from test body + sp<BufferQueue> mBQ; + sp<DummyConsumer> mDC; + +protected: // accessible from test body + sp<IGraphicBufferProducer> mProducer; + sp<IGraphicBufferConsumer> mConsumer; +}; + +TEST_F(IGraphicBufferProducerTest, ConnectFirst_ReturnsError) { + IGraphicBufferProducer::QueueBufferOutput output; + + // NULL output returns BAD_VALUE + EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN, + TEST_API, + TEST_CONTROLLED_BY_APP, + /*output*/NULL)); + + // Invalid API returns bad value + EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN, + /*api*/0xDEADBEEF, + TEST_CONTROLLED_BY_APP, + &output)); + + // TODO: get a token from a dead process somehow +} + +TEST_F(IGraphicBufferProducerTest, ConnectAgain_ReturnsError) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + // Can't connect when there is already a producer connected + IGraphicBufferProducer::QueueBufferOutput output; + EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN, + TEST_API, + TEST_CONTROLLED_BY_APP, + &output)); + + ASSERT_OK(mConsumer->consumerDisconnect()); + // Can't connect when IGBP is abandoned + EXPECT_EQ(NO_INIT, mProducer->connect(TEST_TOKEN, + TEST_API, + TEST_CONTROLLED_BY_APP, + &output)); +} + +TEST_F(IGraphicBufferProducerTest, Disconnect_Succeeds) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + ASSERT_OK(mProducer->disconnect(TEST_API)); +} + + +TEST_F(IGraphicBufferProducerTest, Disconnect_ReturnsError) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + // Must disconnect with same API number + ASSERT_EQ(BAD_VALUE, mProducer->disconnect(TEST_API_OTHER)); + // API must not be out of range + ASSERT_EQ(BAD_VALUE, mProducer->disconnect(/*api*/0xDEADBEEF)); + + // TODO: somehow kill mProducer so that this returns DEAD_OBJECT +} + +TEST_F(IGraphicBufferProducerTest, Query_Succeeds) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + // TODO: Make these constants in header + const int DEFAULT_CONSUMER_USAGE_BITS = 0; + + int value = -1; + EXPECT_OK(mProducer->query(NATIVE_WINDOW_WIDTH, &value)); + EXPECT_EQ(DEFAULT_WIDTH, value); + + EXPECT_OK(mProducer->query(NATIVE_WINDOW_HEIGHT, &value)); + EXPECT_EQ(DEFAULT_HEIGHT, value); + + EXPECT_OK(mProducer->query(NATIVE_WINDOW_FORMAT, &value)); + EXPECT_EQ(DEFAULT_FORMAT, value); + + EXPECT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value)); + EXPECT_LE(0, value); + EXPECT_GE(BufferQueue::NUM_BUFFER_SLOTS, value); + + EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value)); + EXPECT_FALSE(value); // Can't run behind when we haven't touched the queue + + EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value)); + EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, value); + +} + +TEST_F(IGraphicBufferProducerTest, Query_ReturnsError) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + // One past the end of the last 'query' enum value. Update this if we add more enums. + const int NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE = NATIVE_WINDOW_CONSUMER_USAGE_BITS + 1; + + int value; + // What was out of range + EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/-1, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/0xDEADBEEF, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE, &value)); + + // Some enums from window.h are 'invalid' + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_CONCRETE_TYPE, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_WIDTH, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_HEIGHT, &value)); + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value)); + // TODO: Consider documented the above enums as unsupported or make a new enum for IGBP + + // Value was NULL + EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/NULL)); + + ASSERT_OK(mConsumer->consumerDisconnect()); + + // BQ was abandoned + EXPECT_EQ(NO_INIT, mProducer->query(NATIVE_WINDOW_FORMAT, &value)); + + // TODO: other things in window.h that are supported by Surface::query + // but not by BufferQueue::query +} + +// TODO: queue under more complicated situations not involving just a single buffer +TEST_F(IGraphicBufferProducerTest, Queue_Succeeds) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + int dequeuedSlot = -1; + sp<Fence> dequeuedFence; + + // XX: OK to assume first call returns this flag or not? Not really documented. + ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence, + QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS)); + + EXPECT_LE(0, dequeuedSlot); + EXPECT_GT(BufferQueue::NUM_BUFFER_SLOTS, dequeuedSlot); + + // Request the buffer (pre-requisite for queueing) + sp<GraphicBuffer> dequeuedBuffer; + ASSERT_OK(mProducer->requestBuffer(dequeuedSlot, &dequeuedBuffer)); + + // A generic "valid" input + IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput(); + IGraphicBufferProducer::QueueBufferOutput output; + + // Queue the buffer back into the BQ + ASSERT_OK(mProducer->queueBuffer(dequeuedSlot, input, &output)); + + { + uint32_t width; + uint32_t height; + uint32_t transformHint; + uint32_t numPendingBuffers; + + output.deflate(&width, &height, &transformHint, &numPendingBuffers); + + EXPECT_EQ(DEFAULT_WIDTH, width); + EXPECT_EQ(DEFAULT_HEIGHT, height); + EXPECT_EQ(DEFAULT_TRANSFORM_HINT, transformHint); + EXPECT_EQ(1, numPendingBuffers); // since queueBuffer was called exactly once + } + + // Buffer was not in the dequeued state + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); +} + +TEST_F(IGraphicBufferProducerTest, Queue_ReturnsError) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + // Invalid slot number + { + // A generic "valid" input + IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/-1, input, &output)); + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0xDEADBEEF, input, &output)); + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueue::NUM_BUFFER_SLOTS, + input, &output)); + } + + // Slot was not in the dequeued state (all slots start out in Free state) + { + IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0, input, &output)); + } + + // Put the slot into the "dequeued" state for the rest of the test + int dequeuedSlot = -1; + sp<Fence> dequeuedFence; + + ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence, + QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS)); + + // Slot was enqueued without requesting a buffer + { + IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); + } + + // Request the buffer so that the rest of the tests don't fail on earlier checks. + sp<GraphicBuffer> dequeuedBuffer; + ASSERT_OK(mProducer->requestBuffer(dequeuedSlot, &dequeuedBuffer)); + + // Fence was NULL + { + sp<Fence> nullFence = NULL; + + IGraphicBufferProducer::QueueBufferInput input = + QueueBufferInputBuilder().setFence(nullFence).build(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); + } + + // Scaling mode was unknown + { + IGraphicBufferProducer::QueueBufferInput input = + QueueBufferInputBuilder().setScalingMode(-1).build(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); + + input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build(); + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); + } + + // Crop rect is out of bounds of the buffer dimensions + { + IGraphicBufferProducer::QueueBufferInput input = + QueueBufferInputBuilder().setCrop(Rect(DEFAULT_WIDTH + 1, DEFAULT_HEIGHT + 1)) + .build(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output)); + } + + // Abandon the buffer queue so that the last test fails + ASSERT_OK(mConsumer->consumerDisconnect()); + + // The buffer queue has been abandoned. + { + IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput(); + IGraphicBufferProducer::QueueBufferOutput output; + + EXPECT_EQ(NO_INIT, mProducer->queueBuffer(dequeuedSlot, input, &output)); + } +} + +TEST_F(IGraphicBufferProducerTest, CancelBuffer_DoesntCrash) { + ASSERT_NO_FATAL_FAILURE(ConnectProducer()); + + int dequeuedSlot = -1; + sp<Fence> dequeuedFence; + + ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence, + QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS)); + + // No return code, but at least test that it doesn't blow up... + // TODO: add a return code + mProducer->cancelBuffer(dequeuedSlot, dequeuedFence); +} + +TEST_F(IGraphicBufferProducerTest, SetBufferCount_Succeeds) { + + // The producer does not wish to set a buffer count + EXPECT_OK(mProducer->setBufferCount(0)) << "bufferCount: " << 0; + // TODO: how to test "0" buffer count? + + int minBuffers; + ASSERT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minBuffers)); + + // The MIN_UNDEQUEUED_BUFFERS limit is exclusive, so need to increment by at least 1 + minBuffers++; + + ASSERT_OK(mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers; + + std::vector<DequeueBufferResult> dequeueList; + + // Should now be able to dequeue up to minBuffers times + for (int i = 0; i < minBuffers; ++i) { + DequeueBufferResult result; + + EXPECT_LE(OK, + dequeueBuffer(QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS, &result)) + << "iteration: " << i << ", slot: " << result.slot; + + dequeueList.push_back(result); + } + + // Cancel every buffer, so we can set buffer count again + for (int i = 0; i < minBuffers; ++i) { + DequeueBufferResult& result = dequeueList[i]; + mProducer->cancelBuffer(result.slot, result.fence); + } + + ASSERT_OK(mProducer->setBufferCount(BufferQueue::NUM_BUFFER_SLOTS)); + + // Should now be able to dequeue up to NUM_BUFFER_SLOTS times + for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; ++i) { + int dequeuedSlot = -1; + sp<Fence> dequeuedFence; + + EXPECT_LE(OK, + mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence, + QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS)) + << "iteration: " << i << ", slot: " << dequeuedSlot; + } +} + +TEST_F(IGraphicBufferProducerTest, SetBufferCount_Fails) { + int minBuffers; + ASSERT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minBuffers)); + + // The MIN_UNDEQUEUED_BUFFERS limit is exclusive, so need to increment by at least 1 + minBuffers++; + + // Buffer count was out of range + EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(-1)) << "bufferCount: " << -1; + EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(minBuffers - 1)) << "bufferCount: " << minBuffers - 1; + EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(BufferQueue::NUM_BUFFER_SLOTS + 1)) + << "bufferCount: " << BufferQueue::NUM_BUFFER_SLOTS + 1; + + // Pre-requisite to fail out a valid setBufferCount call + { + int dequeuedSlot = -1; + sp<Fence> dequeuedFence; + + ASSERT_LE(OK, + mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence, + QUEUE_BUFFER_INPUT_ASYNC, + DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + TEST_PRODUCER_USAGE_BITS)) + << "slot: " << dequeuedSlot; + } + + // Client has one or more buffers dequeued + EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers; + + // Abandon buffer queue + ASSERT_OK(mConsumer->consumerDisconnect()); + + // Fail because the buffer queue was abandoned + EXPECT_EQ(NO_INIT, mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers; + +} + +} // namespace android diff --git a/libs/gui/tests/MultiTextureConsumer_test.cpp b/libs/gui/tests/MultiTextureConsumer_test.cpp new file mode 100644 index 0000000..853c25c --- /dev/null +++ b/libs/gui/tests/MultiTextureConsumer_test.cpp @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#define LOG_TAG "MultiTextureConsumer_test" +//#define LOG_NDEBUG 0 + +#include "GLTest.h" + +#include <gui/GLConsumer.h> +#include <gui/Surface.h> + +#include <android/native_window.h> + +#include <GLES/glext.h> + +namespace android { + +class MultiTextureConsumerTest : public GLTest { +protected: + enum { TEX_ID = 123 }; + + virtual void SetUp() { + GLTest::SetUp(); + sp<BufferQueue> bq = new BufferQueue(); + mGlConsumer = new GLConsumer(bq, TEX_ID); + mSurface = new Surface(bq); + mANW = mSurface.get(); + + } + virtual void TearDown() { + GLTest::TearDown(); + } + virtual EGLint const* getContextAttribs() { + return NULL; + } + virtual EGLint const* getConfigAttribs() { + static EGLint sDefaultConfigAttribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE }; + + return sDefaultConfigAttribs; + } + sp<GLConsumer> mGlConsumer; + sp<Surface> mSurface; + ANativeWindow* mANW; +}; + +TEST_F(MultiTextureConsumerTest, EGLImageTargetWorks) { + ANativeWindow_Buffer buffer; + + ASSERT_EQ(native_window_set_usage(mANW, GRALLOC_USAGE_SW_WRITE_OFTEN), NO_ERROR); + ASSERT_EQ(native_window_set_buffers_format(mANW, HAL_PIXEL_FORMAT_RGBA_8888), NO_ERROR); + + glShadeModel(GL_FLAT); + glDisable(GL_DITHER); + glDisable(GL_CULL_FACE); + glViewport(0, 0, getSurfaceWidth(), getSurfaceHeight()); + glOrthof(0, getSurfaceWidth(), 0, getSurfaceHeight(), 0, 1); + glEnableClientState(GL_VERTEX_ARRAY); + glColor4f(1, 1, 1, 1); + + glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID); + glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + uint32_t texel = 0x80808080; + glBindTexture(GL_TEXTURE_2D, TEX_ID+1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texel); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, TEX_ID+1); + glEnable(GL_TEXTURE_2D); + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID); + glEnable(GL_TEXTURE_EXTERNAL_OES); + glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glClear(GL_COLOR_BUFFER_BIT); + for (int i=0 ; i<8 ; i++) { + mSurface->lock(&buffer, NULL); + memset(buffer.bits, (i&7) * 0x20, buffer.stride * buffer.height * 4); + mSurface->unlockAndPost(); + + mGlConsumer->updateTexImage(); + + GLfloat vertices[][2] = { {i*16.0f, 0}, {(i+1)*16.0f, 0}, {(i+1)*16.0f, 16.0f}, {i*16.0f, 16.0f} }; + glVertexPointer(2, GL_FLOAT, 0, vertices); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + } + + for (int i=0 ; i<8 ; i++) { + EXPECT_TRUE(checkPixel(i*16 + 8, 8, i*16, i*16, i*16, i*16, 0)); + } +} + +} // namespace android diff --git a/libs/gui/tests/SRGB_test.cpp b/libs/gui/tests/SRGB_test.cpp new file mode 100644 index 0000000..1077c9d --- /dev/null +++ b/libs/gui/tests/SRGB_test.cpp @@ -0,0 +1,476 @@ +/* + * 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. + */ + +#define LOG_TAG "SRGB_test" +//#define LOG_NDEBUG 0 + +#include "GLTest.h" + +#include <gui/CpuConsumer.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> + +#include <android/native_window.h> + +#include <gtest/gtest.h> + +namespace android { + +class SRGBTest : public ::testing::Test { +protected: + // Class constants + enum { + DISPLAY_WIDTH = 512, + DISPLAY_HEIGHT = 512, + PIXEL_SIZE = 4, // bytes or components + DISPLAY_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT * PIXEL_SIZE, + ALPHA_VALUE = 223, // should be in [0, 255] + TOLERANCE = 1, + }; + static const char SHOW_DEBUG_STRING[]; + + SRGBTest() : + mInputSurface(), mCpuConsumer(), mLockedBuffer(), + mEglDisplay(EGL_NO_DISPLAY), mEglConfig(), + mEglContext(EGL_NO_CONTEXT), mEglSurface(EGL_NO_SURFACE), + mComposerClient(), mSurfaceControl(), mOutputSurface() { + } + + virtual ~SRGBTest() { + if (mEglDisplay != EGL_NO_DISPLAY) { + if (mEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mEglSurface); + } + if (mEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mEglContext); + } + eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglTerminate(mEglDisplay); + } + } + + virtual void SetUp() { + mBufferQueue = new BufferQueue(); + ASSERT_EQ(NO_ERROR, mBufferQueue->setDefaultBufferSize( + DISPLAY_WIDTH, DISPLAY_HEIGHT)); + mCpuConsumer = new CpuConsumer(mBufferQueue, 1); + String8 name("CpuConsumer_for_SRGBTest"); + mCpuConsumer->setName(name); + mInputSurface = new Surface(mBufferQueue); + + ASSERT_NO_FATAL_FAILURE(createEGLSurface(mInputSurface.get())); + ASSERT_NO_FATAL_FAILURE(createDebugSurface()); + } + + virtual void TearDown() { + ASSERT_NO_FATAL_FAILURE(copyToDebugSurface()); + ASSERT_TRUE(mLockedBuffer.data != NULL); + ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer)); + } + + static float linearToSRGB(float l) { + if (l <= 0.0031308f) { + return l * 12.92f; + } else { + return 1.055f * pow(l, (1 / 2.4f)) - 0.055f; + } + } + + static float srgbToLinear(float s) { + if (s <= 0.04045) { + return s / 12.92f; + } else { + return pow(((s + 0.055f) / 1.055f), 2.4f); + } + } + + static uint8_t srgbToLinear(uint8_t u) { + float f = u / 255.0f; + return static_cast<uint8_t>(srgbToLinear(f) * 255.0f + 0.5f); + } + + void fillTexture(bool writeAsSRGB) { + uint8_t* textureData = new uint8_t[DISPLAY_SIZE]; + + for (int y = 0; y < DISPLAY_HEIGHT; ++y) { + for (int x = 0; x < DISPLAY_WIDTH; ++x) { + float realValue = static_cast<float>(x) / (DISPLAY_WIDTH - 1); + realValue *= ALPHA_VALUE / 255.0f; // Premultiply by alpha + if (writeAsSRGB) { + realValue = linearToSRGB(realValue); + } + + int offset = (y * DISPLAY_WIDTH + x) * PIXEL_SIZE; + for (int c = 0; c < 3; ++c) { + uint8_t intValue = static_cast<uint8_t>( + realValue * 255.0f + 0.5f); + textureData[offset + c] = intValue; + } + textureData[offset + 3] = ALPHA_VALUE; + } + } + + glTexImage2D(GL_TEXTURE_2D, 0, writeAsSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, + textureData); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + + delete[] textureData; + } + + void initShaders() { + static const char vertexSource[] = + "attribute vec4 vPosition;\n" + "varying vec2 texCoords;\n" + "void main() {\n" + " texCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n" + " gl_Position = vPosition;\n" + "}\n"; + + static const char fragmentSource[] = + "precision mediump float;\n" + "uniform sampler2D texSampler;\n" + "varying vec2 texCoords;\n" + "void main() {\n" + " gl_FragColor = texture2D(texSampler, texCoords);\n" + "}\n"; + + GLuint program; + { + SCOPED_TRACE("Creating shader program"); + ASSERT_NO_FATAL_FAILURE(GLTest::createProgram( + vertexSource, fragmentSource, &program)); + } + + GLint positionHandle = glGetAttribLocation(program, "vPosition"); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + ASSERT_NE(-1, positionHandle); + + GLint samplerHandle = glGetUniformLocation(program, "texSampler"); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + ASSERT_NE(-1, samplerHandle); + + static const GLfloat vertices[] = { + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + 1.0f, 1.0f, + }; + + glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 0, vertices); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glEnableVertexAttribArray(positionHandle); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + + glUseProgram(program); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glUniform1i(samplerHandle, 0); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + + GLuint textureHandle; + glGenTextures(1, &textureHandle); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glBindTexture(GL_TEXTURE_2D, textureHandle); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + } + + void drawTexture(bool asSRGB, GLint x, GLint y, GLsizei width, + GLsizei height) { + ASSERT_NO_FATAL_FAILURE(fillTexture(asSRGB)); + glViewport(x, y, width, height); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + ASSERT_EQ(GL_NO_ERROR, glGetError()); + } + + void checkLockedBuffer(PixelFormat format) { + ASSERT_EQ(mLockedBuffer.format, format); + ASSERT_EQ(mLockedBuffer.width, DISPLAY_WIDTH); + ASSERT_EQ(mLockedBuffer.height, DISPLAY_HEIGHT); + } + + static bool withinTolerance(int a, int b) { + int diff = a - b; + return diff >= 0 ? diff <= TOLERANCE : -diff <= TOLERANCE; + } + + // Primary producer and consumer + sp<BufferQueue> mBufferQueue; + sp<Surface> mInputSurface; + sp<CpuConsumer> mCpuConsumer; + CpuConsumer::LockedBuffer mLockedBuffer; + + EGLDisplay mEglDisplay; + EGLConfig mEglConfig; + EGLContext mEglContext; + EGLSurface mEglSurface; + + // Auxiliary display output + sp<SurfaceComposerClient> mComposerClient; + sp<SurfaceControl> mSurfaceControl; + sp<Surface> mOutputSurface; + +private: + void createEGLSurface(Surface* inputSurface) { + mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); + + EXPECT_TRUE(eglInitialize(mEglDisplay, NULL, NULL)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + static const EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE }; + + EGLint numConfigs = 0; + EXPECT_TRUE(eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1, + &numConfigs)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_GT(numConfigs, 0); + + static const EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE } ; + + mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, + contextAttribs); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mEglContext); + + mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, + inputSurface, NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface); + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + } + + void createDebugSurface() { + if (getenv(SHOW_DEBUG_STRING) == NULL) return; + + mComposerClient = new SurfaceComposerClient; + ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); + + mSurfaceControl = mComposerClient->createSurface( + String8("SRGBTest Surface"), DISPLAY_WIDTH, DISPLAY_HEIGHT, + PIXEL_FORMAT_RGBA_8888); + + ASSERT_TRUE(mSurfaceControl != NULL); + ASSERT_TRUE(mSurfaceControl->isValid()); + + SurfaceComposerClient::openGlobalTransaction(); + ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF)); + ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); + SurfaceComposerClient::closeGlobalTransaction(); + + ANativeWindow_Buffer outBuffer; + ARect inOutDirtyBounds; + mOutputSurface = mSurfaceControl->getSurface(); + mOutputSurface->lock(&outBuffer, &inOutDirtyBounds); + uint8_t* bytePointer = reinterpret_cast<uint8_t*>(outBuffer.bits); + for (int y = 0; y < outBuffer.height; ++y) { + int rowOffset = y * outBuffer.stride; // pixels + for (int x = 0; x < outBuffer.width; ++x) { + int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes + for (int c = 0; c < PIXEL_SIZE; ++c) { + int offset = colOffset + c; + bytePointer[offset] = ((c + 1) * 56) - 1; + } + } + } + mOutputSurface->unlockAndPost(); + } + + void copyToDebugSurface() { + if (!mOutputSurface.get()) return; + + size_t bufferSize = mLockedBuffer.height * mLockedBuffer.stride * + PIXEL_SIZE; + + ANativeWindow_Buffer outBuffer; + ARect outBufferBounds; + mOutputSurface->lock(&outBuffer, &outBufferBounds); + ASSERT_EQ(mLockedBuffer.width, outBuffer.width); + ASSERT_EQ(mLockedBuffer.height, outBuffer.height); + ASSERT_EQ(mLockedBuffer.stride, outBuffer.stride); + + if (mLockedBuffer.format == outBuffer.format) { + memcpy(outBuffer.bits, mLockedBuffer.data, bufferSize); + } else { + ASSERT_EQ(mLockedBuffer.format, PIXEL_FORMAT_sRGB_A_8888); + ASSERT_EQ(outBuffer.format, PIXEL_FORMAT_RGBA_8888); + uint8_t* outPointer = reinterpret_cast<uint8_t*>(outBuffer.bits); + for (int y = 0; y < outBuffer.height; ++y) { + int rowOffset = y * outBuffer.stride; // pixels + for (int x = 0; x < outBuffer.width; ++x) { + int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes + + // RGB are converted + for (int c = 0; c < (PIXEL_SIZE - 1); ++c) { + outPointer[colOffset + c] = srgbToLinear( + mLockedBuffer.data[colOffset + c]); + } + + // Alpha isn't converted + outPointer[colOffset + 3] = + mLockedBuffer.data[colOffset + 3]; + } + } + } + mOutputSurface->unlockAndPost(); + + int sleepSeconds = atoi(getenv(SHOW_DEBUG_STRING)); + sleep(sleepSeconds); + } +}; + +const char SRGBTest::SHOW_DEBUG_STRING[] = "DEBUG_OUTPUT_SECONDS"; + +TEST_F(SRGBTest, GLRenderFromSRGBTexture) { + ASSERT_NO_FATAL_FAILURE(initShaders()); + + // The RGB texture is displayed in the top half + ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, DISPLAY_HEIGHT / 2, + DISPLAY_WIDTH, DISPLAY_HEIGHT / 2)); + + // The SRGB texture is displayed in the bottom half + ASSERT_NO_FATAL_FAILURE(drawTexture(true, 0, 0, + DISPLAY_WIDTH, DISPLAY_HEIGHT / 2)); + + eglSwapBuffers(mEglDisplay, mEglSurface); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Lock + ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer)); + ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888)); + + // Compare a pixel in the middle of each texture + int midSRGBOffset = (DISPLAY_HEIGHT / 4) * mLockedBuffer.stride * + PIXEL_SIZE; + int midRGBOffset = midSRGBOffset * 3; + midRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE; + midSRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE; + for (int c = 0; c < PIXEL_SIZE; ++c) { + int expectedValue = mLockedBuffer.data[midRGBOffset + c]; + int actualValue = mLockedBuffer.data[midSRGBOffset + c]; + ASSERT_PRED2(withinTolerance, expectedValue, actualValue); + } + + // mLockedBuffer is unlocked in TearDown so we can copy data from it to + // the debug surface if necessary +} + +TEST_F(SRGBTest, RenderToSRGBSurface) { + ASSERT_NO_FATAL_FAILURE(initShaders()); + + // By default, the first buffer we write into will be RGB + + // Render an RGB texture across the whole surface + ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0, + DISPLAY_WIDTH, DISPLAY_HEIGHT)); + eglSwapBuffers(mEglDisplay, mEglSurface); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Lock + ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer)); + ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888)); + + // Save the values of the middle pixel for later comparison against SRGB + uint8_t values[PIXEL_SIZE] = {}; + int middleOffset = (DISPLAY_HEIGHT / 2) * mLockedBuffer.stride * + PIXEL_SIZE; + middleOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE; + for (int c = 0; c < PIXEL_SIZE; ++c) { + values[c] = mLockedBuffer.data[middleOffset + c]; + } + + // Unlock + ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer)); + + // Switch to SRGB window surface +#define EGL_GL_COLORSPACE_KHR EGL_VG_COLORSPACE +#define EGL_GL_COLORSPACE_SRGB_KHR EGL_VG_COLORSPACE_sRGB + + static const int srgbAttribs[] = { + EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, + EGL_NONE, + }; + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, + mInputSurface.get(), srgbAttribs); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mEglSurface); + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Render the texture again + ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0, + DISPLAY_WIDTH, DISPLAY_HEIGHT)); + eglSwapBuffers(mEglDisplay, mEglSurface); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Lock + ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer)); + + // Make sure we actually got the SRGB buffer on the consumer side + ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_sRGB_A_8888)); + + // Verify that the stored value is the same, accounting for RGB/SRGB + for (int c = 0; c < PIXEL_SIZE; ++c) { + // The alpha value should be equivalent before linear->SRGB + float rgbAsSRGB = (c == 3) ? values[c] / 255.0f : + linearToSRGB(values[c] / 255.0f); + int expectedValue = rgbAsSRGB * 255.0f + 0.5f; + int actualValue = mLockedBuffer.data[middleOffset + c]; + ASSERT_PRED2(withinTolerance, expectedValue, actualValue); + } + + // mLockedBuffer is unlocked in TearDown so we can copy data from it to + // the debug surface if necessary +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureFBO.h b/libs/gui/tests/SurfaceTextureFBO.h new file mode 100644 index 0000000..7f1ae84 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureFBO.h @@ -0,0 +1,75 @@ +/* + * 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_SURFACE_TEXTURE_FBO_H +#define ANDROID_SURFACE_TEXTURE_FBO_H + +#include "SurfaceTextureGL.h" + +#include <GLES2/gl2.h> + +namespace android { + +class SurfaceTextureFBOTest : public SurfaceTextureGLTest { +protected: + virtual void SetUp() { + SurfaceTextureGLTest::SetUp(); + + glGenFramebuffers(1, &mFbo); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + glGenTextures(1, &mFboTex); + glBindTexture(GL_TEXTURE_2D, mFboTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(), + getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + glBindFramebuffer(GL_FRAMEBUFFER, mFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, mFboTex, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + } + + virtual void TearDown() { + SurfaceTextureGLTest::TearDown(); + + glDeleteTextures(1, &mFboTex); + glDeleteFramebuffers(1, &mFbo); + } + + GLuint mFbo; + GLuint mFboTex; +}; + +void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, + uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + const size_t PIXEL_SIZE = 4; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + off_t offset = (y * stride + x) * PIXEL_SIZE; + buf[offset + 0] = r; + buf[offset + 1] = g; + buf[offset + 2] = b; + buf[offset + 3] = a; + } + } +} + +} // namespace android + +#endif diff --git a/libs/gui/tests/SurfaceTextureFBO_test.cpp b/libs/gui/tests/SurfaceTextureFBO_test.cpp new file mode 100644 index 0000000..b165ae6 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureFBO_test.cpp @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceTextureFBO_test" +//#define LOG_NDEBUG 0 + +#include "SurfaceTextureFBO.h" + +namespace android { + +// This test is intended to verify that proper synchronization is done when +// rendering into an FBO. +TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) { + const int texWidth = 64; + const int texHeight = 64; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + android_native_buffer_t* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + // Fill the buffer with green + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255, + 0, 255); + buf->unlock(); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), + -1)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glBindFramebuffer(GL_FRAMEBUFFER, mFbo); + drawTexture(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + for (int i = 0; i < 4; i++) { + SCOPED_TRACE(String8::format("frame %d", i).string()); + + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + buf = new GraphicBuffer(anb, false); + + // Fill the buffer with red + ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, + (void**)(&img))); + fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0, + 0, 255); + ASSERT_EQ(NO_ERROR, buf->unlock()); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), + buf->getNativeBuffer(), -1)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + drawTexture(); + + EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255)); + } + + glBindFramebuffer(GL_FRAMEBUFFER, mFbo); + + EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255)); +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h new file mode 100644 index 0000000..ac112c4 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGL.h @@ -0,0 +1,73 @@ +/* + * 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_SURFACE_TEXTURE_GL_H +#define ANDROID_SURFACE_TEXTURE_GL_H + +#include "GLTest.h" + +#include "FrameWaiter.h" +#include "TextureRenderer.h" + +#include <gui/GLConsumer.h> +#include <gui/Surface.h> + +namespace android { + +class FrameWaiter; +class GLConsumer; +class TextureRenderer; + +class SurfaceTextureGLTest : public GLTest { +protected: + enum { TEX_ID = 123 }; + + void SetUp() { + GLTest::SetUp(); + sp<BufferQueue> bq = new BufferQueue(); + mBQ = bq; + mST = new GLConsumer(bq, TEX_ID); + mSTC = new Surface(bq); + mANW = mSTC; + mTextureRenderer = new TextureRenderer(TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp()); + mFW = new FrameWaiter; + mST->setFrameAvailableListener(mFW); + } + + void TearDown() { + mTextureRenderer.clear(); + mANW.clear(); + mSTC.clear(); + mST.clear(); + GLTest::TearDown(); + } + + void drawTexture() { + mTextureRenderer->drawTexture(); + } + + sp<BufferQueue> mBQ; + sp<GLConsumer> mST; + sp<Surface> mSTC; + sp<ANativeWindow> mANW; + sp<TextureRenderer> mTextureRenderer; + sp<FrameWaiter> mFW; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/SurfaceTextureGLThreadToGL.h b/libs/gui/tests/SurfaceTextureGLThreadToGL.h new file mode 100644 index 0000000..6410516 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGLThreadToGL.h @@ -0,0 +1,183 @@ +/* + * 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_SURFACE_TEXTURE_GL_THREAD_TO_GL_H +#define ANDROID_SURFACE_TEXTURE_GL_THREAD_TO_GL_H + +#include "SurfaceTextureGLToGL.h" + +namespace android { + +/* + * This test fixture is for testing GL -> GL texture streaming from one thread + * to another. It contains functionality to create a producer thread that will + * perform GL rendering to an ANativeWindow that feeds frames to a + * GLConsumer. Additionally it supports interlocking the producer and + * consumer threads so that a specific sequence of calls can be + * deterministically created by the test. + * + * The intended usage is as follows: + * + * TEST_F(...) { + * class PT : public ProducerThread { + * virtual void render() { + * ... + * swapBuffers(); + * } + * }; + * + * runProducerThread(new PT()); + * + * // The order of these calls will vary from test to test and may include + * // multiple frames and additional operations (e.g. GL rendering from the + * // texture). + * fc->waitForFrame(); + * mST->updateTexImage(); + * fc->finishFrame(); + * } + * + */ +class SurfaceTextureGLThreadToGLTest : public SurfaceTextureGLToGLTest { +protected: + + // ProducerThread is an abstract base class to simplify the creation of + // OpenGL ES frame producer threads. + class ProducerThread : public Thread { + public: + virtual ~ProducerThread() { + } + + void setEglObjects(EGLDisplay producerEglDisplay, + EGLSurface producerEglSurface, + EGLContext producerEglContext) { + mProducerEglDisplay = producerEglDisplay; + mProducerEglSurface = producerEglSurface; + mProducerEglContext = producerEglContext; + } + + virtual bool threadLoop() { + eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext); + render(); + eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + return false; + } + + protected: + virtual void render() = 0; + + void swapBuffers() { + eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface); + } + + EGLDisplay mProducerEglDisplay; + EGLSurface mProducerEglSurface; + EGLContext mProducerEglContext; + }; + + // FrameCondition is a utility class for interlocking between the producer + // and consumer threads. The FrameCondition object should be created and + // destroyed in the consumer thread only. The consumer thread should set + // the FrameCondition as the FrameAvailableListener of the GLConsumer, + // and should call both waitForFrame and finishFrame once for each expected + // frame. + // + // This interlocking relies on the fact that onFrameAvailable gets called + // synchronously from GLConsumer::queueBuffer. + class FrameCondition : public GLConsumer::FrameAvailableListener { + public: + FrameCondition(): + mFrameAvailable(false), + mFrameFinished(false) { + } + + // waitForFrame waits for the next frame to arrive. This should be + // called from the consumer thread once for every frame expected by the + // test. + void waitForFrame() { + Mutex::Autolock lock(mMutex); + ALOGV("+waitForFrame"); + while (!mFrameAvailable) { + mFrameAvailableCondition.wait(mMutex); + } + mFrameAvailable = false; + ALOGV("-waitForFrame"); + } + + // Allow the producer to return from its swapBuffers call and continue + // on to produce the next frame. This should be called by the consumer + // thread once for every frame expected by the test. + void finishFrame() { + Mutex::Autolock lock(mMutex); + ALOGV("+finishFrame"); + mFrameFinished = true; + mFrameFinishCondition.signal(); + ALOGV("-finishFrame"); + } + + // This should be called by GLConsumer on the producer thread. + virtual void onFrameAvailable() { + Mutex::Autolock lock(mMutex); + ALOGV("+onFrameAvailable"); + mFrameAvailable = true; + mFrameAvailableCondition.signal(); + while (!mFrameFinished) { + mFrameFinishCondition.wait(mMutex); + } + mFrameFinished = false; + ALOGV("-onFrameAvailable"); + } + + protected: + bool mFrameAvailable; + bool mFrameFinished; + + Mutex mMutex; + Condition mFrameAvailableCondition; + Condition mFrameFinishCondition; + }; + + virtual void SetUp() { + SurfaceTextureGLToGLTest::SetUp(); + mFC = new FrameCondition(); + mST->setFrameAvailableListener(mFC); + } + + virtual void TearDown() { + if (mProducerThread != NULL) { + mProducerThread->requestExitAndWait(); + } + mProducerThread.clear(); + mFC.clear(); + SurfaceTextureGLToGLTest::TearDown(); + } + + void runProducerThread(const sp<ProducerThread> producerThread) { + ASSERT_TRUE(mProducerThread == NULL); + mProducerThread = producerThread; + producerThread->setEglObjects(mEglDisplay, mProducerEglSurface, + mProducerEglContext); + producerThread->run(); + } + + sp<ProducerThread> mProducerThread; + sp<FrameCondition> mFC; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp b/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp new file mode 100644 index 0000000..9776733 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp @@ -0,0 +1,186 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceTextureGLThreadToGL_test" +//#define LOG_NDEBUG 0 + +#include "SurfaceTextureGLThreadToGL.h" + +namespace android { + +TEST_F(SurfaceTextureGLThreadToGLTest, + UpdateTexImageBeforeFrameFinishedCompletes) { + class PT : public ProducerThread { + virtual void render() { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); + } + }; + + runProducerThread(new PT()); + + mFC->waitForFrame(); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + mFC->finishFrame(); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! +} + +TEST_F(SurfaceTextureGLThreadToGLTest, + UpdateTexImageAfterFrameFinishedCompletes) { + class PT : public ProducerThread { + virtual void render() { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); + } + }; + + runProducerThread(new PT()); + + mFC->waitForFrame(); + mFC->finishFrame(); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! +} + +TEST_F(SurfaceTextureGLThreadToGLTest, + RepeatedUpdateTexImageBeforeFrameFinishedCompletes) { + enum { NUM_ITERATIONS = 1024 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ALOGV("+swapBuffers"); + swapBuffers(); + ALOGV("-swapBuffers"); + } + } + }; + + runProducerThread(new PT()); + + for (int i = 0; i < NUM_ITERATIONS; i++) { + mFC->waitForFrame(); + ALOGV("+updateTexImage"); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ALOGV("-updateTexImage"); + mFC->finishFrame(); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! + } +} + +TEST_F(SurfaceTextureGLThreadToGLTest, + RepeatedUpdateTexImageAfterFrameFinishedCompletes) { + enum { NUM_ITERATIONS = 1024 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ALOGV("+swapBuffers"); + swapBuffers(); + ALOGV("-swapBuffers"); + } + } + }; + + runProducerThread(new PT()); + + for (int i = 0; i < NUM_ITERATIONS; i++) { + mFC->waitForFrame(); + mFC->finishFrame(); + ALOGV("+updateTexImage"); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ALOGV("-updateTexImage"); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! + } +} + +// XXX: This test is disabled because it is currently hanging on some devices. +TEST_F(SurfaceTextureGLThreadToGLTest, + DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) { + enum { NUM_ITERATIONS = 64 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ALOGV("+swapBuffers"); + swapBuffers(); + ALOGV("-swapBuffers"); + } + } + }; + + ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2)); + + runProducerThread(new PT()); + + // Allow three frames to be rendered and queued before starting the + // rendering in this thread. For the latter two frames we don't call + // updateTexImage so the next dequeue from the producer thread will block + // waiting for a frame to become available. + mFC->waitForFrame(); + mFC->finishFrame(); + + // We must call updateTexImage to consume the first frame so that the + // SurfaceTexture is able to reduce the buffer count to 2. This is because + // the GL driver may dequeue a buffer when the EGLSurface is created, and + // that happens before we call setDefaultMaxBufferCount. It's possible that the + // driver does not dequeue a buffer at EGLSurface creation time, so we + // cannot rely on this to cause the second dequeueBuffer call to block. + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + mFC->waitForFrame(); + mFC->finishFrame(); + mFC->waitForFrame(); + mFC->finishFrame(); + + // Sleep for 100ms to allow the producer thread's dequeueBuffer call to + // block waiting for a buffer to become available. + usleep(100000); + + // Render and present a number of images. This thread should not be blocked + // by the fact that the producer thread is blocking in dequeue. + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mEglSurface); + } + + // Consume the two pending buffers to unblock the producer thread. + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + // Consume the remaining buffers from the producer thread. + for (int i = 0; i < NUM_ITERATIONS-3; i++) { + mFC->waitForFrame(); + mFC->finishFrame(); + ALOGV("+updateTexImage"); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ALOGV("-updateTexImage"); + } +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureGLToGL.h b/libs/gui/tests/SurfaceTextureGLToGL.h new file mode 100644 index 0000000..5a2eff3 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGLToGL.h @@ -0,0 +1,65 @@ +/* + * 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_SURFACE_TEXTURE_GL_TO_GL_H +#define ANDROID_SURFACE_TEXTURE_GL_TO_GL_H + +#include "SurfaceTextureGL.h" + +namespace android { + +/* + * This test fixture is for testing GL -> GL texture streaming. It creates an + * EGLSurface and an EGLContext for the image producer to use. + */ +class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest { +protected: + SurfaceTextureGLToGLTest(): + mProducerEglSurface(EGL_NO_SURFACE), + mProducerEglContext(EGL_NO_CONTEXT) { + } + + virtual void SetUp() { + SurfaceTextureGLTest::SetUp(); + + mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, + mANW.get(), NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); + + mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig, + EGL_NO_CONTEXT, getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); + } + + virtual void TearDown() { + if (mProducerEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mProducerEglContext); + } + if (mProducerEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mProducerEglSurface); + } + SurfaceTextureGLTest::TearDown(); + } + + EGLSurface mProducerEglSurface; + EGLContext mProducerEglContext; +}; + +} // namespace android + +#endif diff --git a/libs/gui/tests/SurfaceTextureGLToGL_test.cpp b/libs/gui/tests/SurfaceTextureGLToGL_test.cpp new file mode 100644 index 0000000..f4c7961 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGLToGL_test.cpp @@ -0,0 +1,502 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceTextureGLToGL_test" +//#define LOG_NDEBUG 0 + +#include "SurfaceTextureGLToGL.h" + +namespace android { + +TEST_F(SurfaceTextureGLToGLTest, TransformHintGetsRespected) { + const uint32_t texWidth = 32; + const uint32_t texHeight = 64; + + mST->setDefaultBufferSize(texWidth, texHeight); + mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); + + // This test requires 3 buffers to avoid deadlock because we're + // both producer and consumer, and only using one thread. + mST->setDefaultMaxBufferCount(3); + + // Do the producer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Start a buffer with our chosen size and transform hint moving + // through the system. + glClear(GL_COLOR_BUFFER_BIT); // give the driver something to do + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + mST->updateTexImage(); // consume it + // Swap again. + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + mST->updateTexImage(); + + // The current buffer should either show the effects of the transform + // hint (in the form of an inverse transform), or show that the + // transform hint has been ignored. + sp<GraphicBuffer> buf = mST->getCurrentBuffer(); + if (mST->getCurrentTransform() == NATIVE_WINDOW_TRANSFORM_ROT_270) { + ASSERT_EQ(texWidth, buf->getHeight()); + ASSERT_EQ(texHeight, buf->getWidth()); + } else { + ASSERT_EQ(texWidth, buf->getWidth()); + ASSERT_EQ(texHeight, buf->getHeight()); + } + + // Reset the transform hint and confirm that it takes. + mST->setTransformHint(0); + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + mST->updateTexImage(); + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + mST->updateTexImage(); + + buf = mST->getCurrentBuffer(); + ASSERT_EQ((uint32_t) 0, mST->getCurrentTransform()); + ASSERT_EQ(texWidth, buf->getWidth()); + ASSERT_EQ(texHeight, buf->getHeight()); +} + +TEST_F(SurfaceTextureGLToGLTest, TexturingFromGLFilledRGBABufferPow2) { + const int texWidth = 64; + const int texHeight = 64; + + mST->setDefaultBufferSize(texWidth, texHeight); + + // This test requires 3 buffers to complete run on a single thread. + mST->setDefaultMaxBufferCount(3); + + // Do the producer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // This is needed to ensure we pick up a buffer of the correct size. + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + glClearColor(0.6, 0.6, 0.6, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(4, 4, 4, 4); + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(24, 48, 4, 4); + glClearColor(0.0, 1.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glScissor(37, 17, 4, 4); + glClearColor(0.0, 0.0, 1.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + // Do the consumer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + glDisable(GL_SCISSOR_TEST); + + // Skip the first frame, which was empty + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153)); + + EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255)); + EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255)); + EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255)); + EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153)); +} + +TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceUnrefsBuffers) { + sp<GraphicBuffer> buffers[2]; + + // This test requires async mode to run on a single thread. + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + for (int i = 0; i < 2; i++) { + // Produce a frame + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + // Consume a frame + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mFW->waitForFrame(); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + buffers[i] = mST->getCurrentBuffer(); + } + + // Destroy the GL texture object to release its ref on buffers[2]. + GLuint texID = TEX_ID; + glDeleteTextures(1, &texID); + + // Destroy the EGLSurface + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mProducerEglSurface = EGL_NO_SURFACE; + + // This test should have the only reference to buffer 0. + EXPECT_EQ(1, buffers[0]->getStrongCount()); + + // The GLConsumer should hold a single reference to buffer 1 in its + // mCurrentBuffer member. All of the references in the slots should have + // been released. + EXPECT_EQ(2, buffers[1]->getStrongCount()); +} + +TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) { + sp<GraphicBuffer> buffers[3]; + + // This test requires async mode to run on a single thread. + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + for (int i = 0; i < 3; i++) { + // Produce a frame + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Consume a frame + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mFW->waitForFrame(); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + buffers[i] = mST->getCurrentBuffer(); + } + + // Abandon the GLConsumer, releasing the ref that the GLConsumer has + // on buffers[2]. + mST->abandon(); + + // Destroy the GL texture object to release its ref on buffers[2]. + GLuint texID = TEX_ID; + glDeleteTextures(1, &texID); + + // Destroy the EGLSurface. + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mProducerEglSurface = EGL_NO_SURFACE; + + EXPECT_EQ(1, buffers[0]->getStrongCount()); + EXPECT_EQ(1, buffers[1]->getStrongCount()); + + // Depending on how lazily the GL driver dequeues buffers, we may end up + // with either two or three total buffers. If there are three, make sure + // the last one was properly down-ref'd. + if (buffers[2] != buffers[0]) { + EXPECT_EQ(1, buffers[2]->getStrongCount()); + } +} + +TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentBeforeConsumerDeathUnrefsBuffers) { + sp<GraphicBuffer> buffer; + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + + // Produce a frame + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Destroy the EGLSurface. + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mProducerEglSurface = EGL_NO_SURFACE; + mSTC.clear(); + mANW.clear(); + mTextureRenderer.clear(); + + // Consume a frame + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + buffer = mST->getCurrentBuffer(); + + // Destroy the GL texture object to release its ref + GLuint texID = TEX_ID; + glDeleteTextures(1, &texID); + + // make un-current, all references to buffer should be gone + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT)); + + // Destroy consumer + mST.clear(); + + EXPECT_EQ(1, buffer->getStrongCount()); +} + +TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentAfterConsumerDeathUnrefsBuffers) { + sp<GraphicBuffer> buffer; + + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + + // Produce a frame + glClear(GL_COLOR_BUFFER_BIT); + EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Destroy the EGLSurface. + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mProducerEglSurface = EGL_NO_SURFACE; + mSTC.clear(); + mANW.clear(); + mTextureRenderer.clear(); + + // Consume a frame + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + buffer = mST->getCurrentBuffer(); + + // Destroy the GL texture object to release its ref + GLuint texID = TEX_ID; + glDeleteTextures(1, &texID); + + // Destroy consumer + mST.clear(); + + // make un-current, all references to buffer should be gone + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT)); + + EXPECT_EQ(1, buffer->getStrongCount()); +} + +TEST_F(SurfaceTextureGLToGLTest, TexturingFromUserSizedGLFilledBuffer) { + enum { texWidth = 64 }; + enum { texHeight = 64 }; + + // This test requires 3 buffers to complete run on a single thread. + mST->setDefaultMaxBufferCount(3); + + // Set the user buffer size. + native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight); + + // Do the producer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // This is needed to ensure we pick up a buffer of the correct size. + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + glClearColor(0.6, 0.6, 0.6, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(4, 4, 1, 1); + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + // Do the consumer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + glDisable(GL_SCISSOR_TEST); + + // Skip the first frame, which was empty + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153)); + + EXPECT_TRUE(checkPixel( 4, 4, 255, 0, 0, 255)); + EXPECT_TRUE(checkPixel( 5, 5, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 3, 3, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(45, 52, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(12, 36, 153, 153, 153, 153)); +} + +TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedUserSizedGLFilledBuffer) { + enum { texWidth = 64 }; + enum { texHeight = 16 }; + + // This test requires 3 buffers to complete run on a single thread. + mST->setDefaultMaxBufferCount(3); + + // Set the transform hint. + mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); + + // Set the user buffer size. + native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight); + + // Do the producer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // This is needed to ensure we pick up a buffer of the correct size and the + // new rotation hint. + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + glClearColor(0.6, 0.6, 0.6, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(24, 4, 1, 1); + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + // Do the consumer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + glDisable(GL_SCISSOR_TEST); + + // Skip the first frame, which was empty + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153)); + + EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255)); + EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153)); +} + +TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedGLFilledBuffer) { + enum { texWidth = 64 }; + enum { texHeight = 16 }; + + // This test requires 3 buffers to complete run on a single thread. + mST->setDefaultMaxBufferCount(3); + + // Set the transform hint. + mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); + + // Set the default buffer size. + mST->setDefaultBufferSize(texWidth, texHeight); + + // Do the producer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // This is needed to ensure we pick up a buffer of the correct size and the + // new rotation hint. + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + glClearColor(0.6, 0.6, 0.6, 0.6); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_SCISSOR_TEST); + glScissor(24, 4, 1, 1); + glClearColor(1.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(mEglDisplay, mProducerEglSurface); + + // Do the consumer side of things + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + glDisable(GL_SCISSOR_TEST); + + // Skip the first frame, which was empty + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153)); + + EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255)); + EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153)); + EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153)); +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp new file mode 100644 index 0000000..25b2319 --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGL_test.cpp @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2011 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. + */ + +#define LOG_TAG "SurfaceTextureGL_test" +//#define LOG_NDEBUG 0 + +#include "SurfaceTextureGL.h" + +#include "DisconnectWaiter.h" +#include "FillBuffer.h" + +namespace android { + +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { + const int texWidth = 64; + const int texHeight = 66; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + // Fill the buffer with the a checkerboard pattern + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + fillYV12Buffer(img, texWidth, texHeight, buf->getStride()); + buf->unlock(); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), + -1)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255, 3)); + EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255, 3)); + EXPECT_TRUE(checkPixel(63, 65, 0, 133, 0, 255, 3)); + EXPECT_TRUE(checkPixel( 0, 65, 255, 127, 255, 255, 3)); + + EXPECT_TRUE(checkPixel(22, 44, 255, 127, 255, 255, 3)); + EXPECT_TRUE(checkPixel(45, 52, 255, 127, 255, 255, 3)); + EXPECT_TRUE(checkPixel(52, 51, 98, 255, 73, 255, 3)); + EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255, 3)); + EXPECT_TRUE(checkPixel(31, 9, 107, 24, 87, 255, 3)); + EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255, 3)); + EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255, 3)); +} + +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferPow2) { + const int texWidth = 64; + const int texHeight = 64; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + // Fill the buffer with the a checkerboard pattern + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + fillYV12Buffer(img, texWidth, texHeight, buf->getStride()); + buf->unlock(); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), + -1)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 0, 133, 0, 255)); + EXPECT_TRUE(checkPixel(63, 0, 255, 127, 255, 255)); + EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255)); + EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255)); + + EXPECT_TRUE(checkPixel(22, 19, 100, 255, 74, 255)); + EXPECT_TRUE(checkPixel(45, 11, 100, 255, 74, 255)); + EXPECT_TRUE(checkPixel(52, 12, 155, 0, 181, 255)); + EXPECT_TRUE(checkPixel( 7, 32, 150, 237, 170, 255)); + EXPECT_TRUE(checkPixel(31, 54, 0, 71, 117, 255)); + EXPECT_TRUE(checkPixel(29, 28, 0, 133, 0, 255)); + EXPECT_TRUE(checkPixel(36, 41, 100, 232, 255, 255)); +} + +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { + const int texWidth = 64; + const int texHeight = 66; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + android_native_rect_t crops[] = { + {4, 6, 22, 36}, + {0, 6, 22, 36}, + {4, 0, 22, 36}, + {4, 6, texWidth, 36}, + {4, 6, 22, texHeight}, + }; + + for (int i = 0; i < 5; i++) { + const android_native_rect_t& crop(crops[i]); + SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }", + crop.left, crop.top, crop.right, crop.bottom).string()); + + ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop)); + + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + fillYV12BufferRect(img, texWidth, texHeight, buf->getStride(), crop); + buf->unlock(); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), + buf->getNativeBuffer(), -1)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, 64, 64); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(63, 0, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(63, 63, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel( 0, 63, 82, 255, 35, 255)); + + EXPECT_TRUE(checkPixel(25, 14, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(35, 31, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(57, 6, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel( 5, 42, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(32, 33, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(16, 26, 82, 255, 35, 255)); + EXPECT_TRUE(checkPixel(46, 51, 82, 255, 35, 255)); + } +} + +// This test is intended to catch synchronization bugs between the CPU-written +// and GPU-read buffers. +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) { + enum { texWidth = 16 }; + enum { texHeight = 16 }; + enum { numFrames = 1024 }; + + ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2)); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_WRITE_OFTEN)); + + struct TestPixel { + int x; + int y; + }; + const TestPixel testPixels[] = { + { 4, 11 }, + { 12, 14 }, + { 7, 2 }, + }; + enum {numTestPixels = sizeof(testPixels) / sizeof(testPixels[0])}; + + class ProducerThread : public Thread { + public: + ProducerThread(const sp<ANativeWindow>& anw, + const TestPixel* testPixels): + mANW(anw), + mTestPixels(testPixels) { + } + + virtual ~ProducerThread() { + } + + virtual bool threadLoop() { + for (int i = 0; i < numFrames; i++) { + ANativeWindowBuffer* anb; + if (native_window_dequeue_buffer_and_wait(mANW.get(), + &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + + const int yuvTexOffsetY = 0; + int stride = buf->getStride(); + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * texHeight; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * texHeight/2; + int yuvTexStrideU = yuvTexStrideV; + + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + + // Gray out all the test pixels first, so we're more likely to + // see a failure if GL is still texturing from the buffer we + // just dequeued. + for (int j = 0; j < numTestPixels; j++) { + int x = mTestPixels[j].x; + int y = mTestPixels[j].y; + uint8_t value = 128; + img[y*stride + x] = value; + } + + // Fill the buffer with gray. + for (int y = 0; y < texHeight; y++) { + for (int x = 0; x < texWidth; x++) { + img[yuvTexOffsetY + y*yuvTexStrideY + x] = 128; + img[yuvTexOffsetU + (y/2)*yuvTexStrideU + x/2] = 128; + img[yuvTexOffsetV + (y/2)*yuvTexStrideV + x/2] = 128; + } + } + + // Set the test pixels to either white or black. + for (int j = 0; j < numTestPixels; j++) { + int x = mTestPixels[j].x; + int y = mTestPixels[j].y; + uint8_t value = 0; + if (j == (i % numTestPixels)) { + value = 255; + } + img[y*stride + x] = value; + } + + buf->unlock(); + if (mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), -1) + != NO_ERROR) { + return false; + } + } + return false; + } + + sp<ANativeWindow> mANW; + const TestPixel* mTestPixels; + }; + + sp<Thread> pt(new ProducerThread(mANW, testPixels)); + pt->run(); + + glViewport(0, 0, texWidth, texHeight); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + // We wait for the first two frames up front so that the producer will be + // likely to dequeue the buffer that's currently being textured from. + mFW->waitForFrame(); + mFW->waitForFrame(); + + for (int i = 0; i < numFrames; i++) { + SCOPED_TRACE(String8::format("frame %d", i).string()); + + // We must wait for each frame to come in because if we ever do an + // updateTexImage call that doesn't consume a newly available buffer + // then the producer and consumer will get out of sync, which will cause + // a deadlock. + if (i > 1) { + mFW->waitForFrame(); + } + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + drawTexture(); + + for (int j = 0; j < numTestPixels; j++) { + int x = testPixels[j].x; + int y = testPixels[j].y; + uint8_t value = 0; + if (j == (i % numTestPixels)) { + // We must y-invert the texture coords + EXPECT_TRUE(checkPixel(x, texHeight-y-1, 255, 255, 255, 255)); + } else { + // We must y-invert the texture coords + EXPECT_TRUE(checkPixel(x, texHeight-y-1, 0, 0, 0, 255)); + } + } + } + + pt->requestExitAndWait(); +} + +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) { + const int texWidth = 64; + const int texHeight = 66; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel(63, 0, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel(63, 65, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35)); + + EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231)); + EXPECT_TRUE(checkPixel(23, 65, 231, 35, 231, 35)); + EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35)); + EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35)); + EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231)); + EXPECT_TRUE(checkPixel(37, 34, 35, 231, 231, 231)); + EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231)); + EXPECT_TRUE(checkPixel(37, 47, 231, 35, 231, 231)); + EXPECT_TRUE(checkPixel(25, 38, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel(49, 6, 35, 231, 35, 35)); + EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231)); + EXPECT_TRUE(checkPixel(27, 26, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel(10, 6, 35, 35, 231, 231)); + EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231)); + EXPECT_TRUE(checkPixel(55, 28, 35, 35, 231, 35)); + EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231)); +} + +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { + const int texWidth = 64; + const int texHeight = 64; + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, texWidth, texHeight); + drawTexture(); + + EXPECT_TRUE(checkPixel( 0, 0, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel(63, 0, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel(63, 63, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel( 0, 63, 35, 35, 35, 35)); + + EXPECT_TRUE(checkPixel(12, 46, 231, 231, 231, 35)); + EXPECT_TRUE(checkPixel(16, 1, 231, 231, 35, 231)); + EXPECT_TRUE(checkPixel(21, 12, 231, 35, 35, 231)); + EXPECT_TRUE(checkPixel(26, 51, 231, 35, 231, 35)); + EXPECT_TRUE(checkPixel( 5, 32, 35, 231, 231, 35)); + EXPECT_TRUE(checkPixel(13, 8, 35, 231, 231, 231)); + EXPECT_TRUE(checkPixel(46, 3, 35, 35, 231, 35)); + EXPECT_TRUE(checkPixel(30, 33, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel( 6, 52, 231, 231, 35, 35)); + EXPECT_TRUE(checkPixel(55, 33, 35, 231, 35, 231)); + EXPECT_TRUE(checkPixel(16, 29, 35, 35, 231, 231)); + EXPECT_TRUE(checkPixel( 1, 30, 35, 35, 35, 231)); + EXPECT_TRUE(checkPixel(41, 37, 35, 35, 231, 231)); + EXPECT_TRUE(checkPixel(46, 29, 231, 231, 35, 35)); + EXPECT_TRUE(checkPixel(15, 25, 35, 231, 35, 231)); + EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35)); +} + +// Tests if GLConsumer and BufferQueue are robust enough +// to handle a special case where updateTexImage is called +// in the middle of disconnect. This ordering is enforced +// by blocking in the disconnect callback. +TEST_F(SurfaceTextureGLTest, DisconnectStressTest) { + + class ProducerThread : public Thread { + public: + ProducerThread(const sp<ANativeWindow>& anw): + mANW(anw) { + } + + virtual ~ProducerThread() { + } + + virtual bool threadLoop() { + ANativeWindowBuffer* anb; + + native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_EGL); + + for (int numFrames =0 ; numFrames < 2; numFrames ++) { + + if (native_window_dequeue_buffer_and_wait(mANW.get(), + &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb, -1) + != NO_ERROR) { + return false; + } + } + + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL); + + return false; + } + + private: + sp<ANativeWindow> mANW; + }; + + sp<DisconnectWaiter> dw(new DisconnectWaiter()); + mBQ->consumerConnect(dw, false); + + + sp<Thread> pt(new ProducerThread(mANW)); + pt->run(); + + // eat a frame so GLConsumer will own an at least one slot + dw->waitForFrame(); + EXPECT_EQ(OK,mST->updateTexImage()); + + dw->waitForFrame(); + // Could fail here as GLConsumer thinks it still owns the slot + // but bufferQueue has released all slots + EXPECT_EQ(OK,mST->updateTexImage()); + + dw->finishDisconnect(); +} + + +// This test ensures that the GLConsumer clears the mCurrentTexture +// when it is disconnected and reconnected. Otherwise it will +// attempt to release a buffer that it does not owned +TEST_F(SurfaceTextureGLTest, DisconnectClearsCurrentTexture) { + ASSERT_EQ(OK, native_window_api_connect(mANW.get(), + NATIVE_WINDOW_API_EGL)); + + ANativeWindowBuffer *anb; + + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + + EXPECT_EQ(OK,mST->updateTexImage()); + EXPECT_EQ(OK,mST->updateTexImage()); + + ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), + NATIVE_WINDOW_API_EGL)); + ASSERT_EQ(OK, native_window_api_connect(mANW.get(), + NATIVE_WINDOW_API_EGL)); + + EXPECT_EQ(OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + + // Will fail here if mCurrentTexture is not cleared properly + mFW->waitForFrame(); + EXPECT_EQ(OK,mST->updateTexImage()); + + ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), + NATIVE_WINDOW_API_EGL)); +} + +TEST_F(SurfaceTextureGLTest, ScaleToWindowMode) { + ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(), + NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW)); + + // The producer image size + ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512)); + + // The consumer image size (16 x 9) ratio + mST->setDefaultBufferSize(1280, 720); + + ASSERT_EQ(OK, native_window_api_connect(mANW.get(), + NATIVE_WINDOW_API_CPU)); + + ANativeWindowBuffer *anb; + + android_native_rect_t odd = {23, 78, 123, 477}; + ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &odd)); + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + mFW->waitForFrame(); + EXPECT_EQ(OK, mST->updateTexImage()); + Rect r = mST->getCurrentCrop(); + assertRectEq(Rect(23, 78, 123, 477), r); + + ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), + NATIVE_WINDOW_API_CPU)); +} + +// This test ensures the scaling mode does the right thing +// ie NATIVE_WINDOW_SCALING_MODE_CROP should crop +// the image such that it has the same aspect ratio as the +// default buffer size +TEST_F(SurfaceTextureGLTest, CroppedScalingMode) { + ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(), + NATIVE_WINDOW_SCALING_MODE_SCALE_CROP)); + + // The producer image size + ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512)); + + // The consumer image size (16 x 9) ratio + mST->setDefaultBufferSize(1280, 720); + + native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU); + + ANativeWindowBuffer *anb; + + // The crop is in the shape of (320, 180) === 16 x 9 + android_native_rect_t standard = {10, 20, 330, 200}; + ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &standard)); + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + mFW->waitForFrame(); + EXPECT_EQ(OK, mST->updateTexImage()); + Rect r = mST->getCurrentCrop(); + // crop should be the same as crop (same aspect ratio) + assertRectEq(Rect(10, 20, 330, 200), r); + + // make this wider then desired aspect 239 x 100 (2.39:1) + android_native_rect_t wide = {20, 30, 259, 130}; + ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &wide)); + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + mFW->waitForFrame(); + EXPECT_EQ(OK, mST->updateTexImage()); + r = mST->getCurrentCrop(); + // crop should be the same height, but have cropped left and right borders + // offset is 30.6 px L+, R- + assertRectEq(Rect(51, 30, 228, 130), r); + + // This image is taller then desired aspect 400 x 300 (4:3) + android_native_rect_t narrow = {0, 0, 400, 300}; + ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &narrow)); + EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); + EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); + mFW->waitForFrame(); + EXPECT_EQ(OK, mST->updateTexImage()); + r = mST->getCurrentCrop(); + // crop should be the same width, but have cropped top and bottom borders + // offset is 37.5 px + assertRectEq(Rect(0, 37, 400, 262), r); + + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); +} + +TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) { + class ProducerThread : public Thread { + public: + ProducerThread(const sp<ANativeWindow>& anw): + mANW(anw), + mDequeueError(NO_ERROR) { + } + + virtual ~ProducerThread() { + } + + virtual bool threadLoop() { + Mutex::Autolock lock(mMutex); + ANativeWindowBuffer* anb; + + // Frame 1 + if (native_window_dequeue_buffer_and_wait(mANW.get(), + &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb, -1) + != NO_ERROR) { + return false; + } + + // Frame 2 + if (native_window_dequeue_buffer_and_wait(mANW.get(), + &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb, -1) + != NO_ERROR) { + return false; + } + + // Frame 3 - error expected + mDequeueError = native_window_dequeue_buffer_and_wait(mANW.get(), + &anb); + return false; + } + + status_t getDequeueError() { + Mutex::Autolock lock(mMutex); + return mDequeueError; + } + + private: + sp<ANativeWindow> mANW; + status_t mDequeueError; + Mutex mMutex; + }; + + ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2)); + + sp<Thread> pt(new ProducerThread(mANW)); + pt->run(); + + mFW->waitForFrame(); + mFW->waitForFrame(); + + // Sleep for 100ms to allow the producer thread's dequeueBuffer call to + // block waiting for a buffer to become available. + usleep(100000); + + mST->abandon(); + + pt->requestExitAndWait(); + ASSERT_EQ(NO_INIT, + reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError()); +} + +TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) { + int texHeight = 16; + ANativeWindowBuffer* anb; + + GLint maxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + // make sure it works with small textures + mST->setDefaultBufferSize(16, texHeight); + EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + EXPECT_EQ(16, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); + EXPECT_EQ(NO_ERROR, mST->updateTexImage()); + + // make sure it works with GL_MAX_TEXTURE_SIZE + mST->setDefaultBufferSize(maxTextureSize, texHeight); + EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + EXPECT_EQ(maxTextureSize, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); + EXPECT_EQ(NO_ERROR, mST->updateTexImage()); + + // make sure it fails with GL_MAX_TEXTURE_SIZE+1 + mST->setDefaultBufferSize(maxTextureSize+1, texHeight); + EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), + &anb)); + EXPECT_EQ(maxTextureSize+1, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); + ASSERT_NE(NO_ERROR, mST->updateTexImage()); +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureMultiContextGL.h b/libs/gui/tests/SurfaceTextureMultiContextGL.h new file mode 100644 index 0000000..7934bbc --- /dev/null +++ b/libs/gui/tests/SurfaceTextureMultiContextGL.h @@ -0,0 +1,84 @@ +/* + * 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_SURFACE_TEXTURE_MULTI_CONTEXT_GL_H +#define ANDROID_SURFACE_TEXTURE_MULTI_CONTEXT_GL_H + +#include "SurfaceTextureGL.h" + +namespace android { + +class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest { +protected: + enum { SECOND_TEX_ID = 123 }; + enum { THIRD_TEX_ID = 456 }; + + SurfaceTextureMultiContextGLTest(): + mSecondEglContext(EGL_NO_CONTEXT) { + } + + virtual void SetUp() { + SurfaceTextureGLTest::SetUp(); + + // Set up the secondary context and texture renderer. + mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig, + EGL_NO_CONTEXT, getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext); + + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp()); + + // Set up the tertiary context and texture renderer. + mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig, + EGL_NO_CONTEXT, getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext); + + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST); + ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp()); + + // Switch back to the primary context to start the tests. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + } + + virtual void TearDown() { + if (mThirdEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mThirdEglContext); + } + if (mSecondEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mSecondEglContext); + } + SurfaceTextureGLTest::TearDown(); + } + + EGLContext mSecondEglContext; + sp<TextureRenderer> mSecondTextureRenderer; + + EGLContext mThirdEglContext; + sp<TextureRenderer> mThirdTextureRenderer; +}; + +} + +#endif diff --git a/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp b/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp new file mode 100644 index 0000000..115a47d --- /dev/null +++ b/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp @@ -0,0 +1,389 @@ +/* + * 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. + */ + +#define LOG_TAG "SurfaceTextureMultiContextGL_test" +//#define LOG_NDEBUG 0 + +#include "SurfaceTextureMultiContextGL.h" + +#include "FillBuffer.h" + +#include <GLES/glext.h> + +namespace android { + +TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Attempt to latch the texture on the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Check that the GL texture was deleted. + EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + DetachFromContextSucceedsAfterProducerDisconnect) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Check that the GL texture was deleted. + EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Attempt to detach from the primary context. + mST->abandon(); + ASSERT_EQ(NO_INIT, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to detach from the primary context again. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Make there be no current display. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Attempt to detach from the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Make current context be incorrect. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Attempt to detach from the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsAfterProducerDisconnect) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsBeforeUpdateTexImage) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(SECOND_TEX_ID, texBinding); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Try to use the texture from the secondary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mSecondTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attempt to attach to the secondary context. + mST->abandon(); + + // Attempt to attach to the primary context. + ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Attempt to attach to the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextFailsWhenAttachedBeforeUpdateTexImage) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Attempt to attach to the primary context. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Make there be no current display. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // Attempt to attach with no context current. + ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Latch the texture contents on the primary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Detach from the secondary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the tertiary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(THIRD_TEX_ID, texBinding); + + // Try to use the texture from the tertiary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mThirdTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachToContextSucceedsTwiceBeforeUpdateTexImage) { + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the secondary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Detach from the secondary context. + ASSERT_EQ(OK, mST->detachFromContext()); + + // Attach to the tertiary context. + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mThirdEglContext)); + ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); + + // Verify that the texture object was created and bound. + GLint texBinding = -1; + glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); + EXPECT_EQ(THIRD_TEX_ID, texBinding); + + // Latch the texture contents on the tertiary context. + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // Try to use the texture from the tertiary context. + glClearColor(0.2, 0.2, 0.2, 0.2); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1, 1); + mThirdTextureRenderer->drawTexture(); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + UpdateTexImageSucceedsForBufferConsumedBeforeDetach) { + ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2)); + + // produce two frames and consume them both on the primary context + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); + + // produce one more frame + ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); + + // Detach from the primary context and attach to the secondary context + ASSERT_EQ(OK, mST->detachFromContext()); + ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mSecondEglContext)); + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + + // Consume final frame on secondary context + mFW->waitForFrame(); + ASSERT_EQ(OK, mST->updateTexImage()); +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp deleted file mode 100644 index e4fba15..0000000 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ /dev/null @@ -1,2816 +0,0 @@ -/* - * Copyright (C) 2011 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. - */ - -#define LOG_TAG "SurfaceTexture_test" -//#define LOG_NDEBUG 0 - -#include <gtest/gtest.h> -#include <gui/GLConsumer.h> -#include <ui/GraphicBuffer.h> -#include <utils/String8.h> -#include <utils/threads.h> - -#include <gui/ISurfaceComposer.h> -#include <gui/Surface.h> -#include <gui/SurfaceComposerClient.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES/gl.h> -#include <GLES/glext.h> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <ui/FramebufferNativeWindow.h> -#include <android/native_window.h> - -namespace android { - -class GLTest : public ::testing::Test { -protected: - - GLTest(): - mEglDisplay(EGL_NO_DISPLAY), - mEglSurface(EGL_NO_SURFACE), - mEglContext(EGL_NO_CONTEXT) { - } - - virtual void SetUp() { - const ::testing::TestInfo* const testInfo = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("Begin test: %s.%s", testInfo->test_case_name(), - testInfo->name()); - - mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); - - EGLint majorVersion; - EGLint minorVersion; - EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - RecordProperty("EglVersionMajor", majorVersion); - RecordProperty("EglVersionMajor", minorVersion); - - EGLint numConfigs = 0; - EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig, - 1, &numConfigs)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS"); - if (displaySecsEnv != NULL) { - mDisplaySecs = atoi(displaySecsEnv); - if (mDisplaySecs < 0) { - mDisplaySecs = 0; - } - } else { - mDisplaySecs = 0; - } - - if (mDisplaySecs > 0) { - mComposerClient = new SurfaceComposerClient; - ASSERT_EQ(NO_ERROR, mComposerClient->initCheck()); - - mSurfaceControl = mComposerClient->createSurface( - String8("Test Surface"), - getSurfaceWidth(), getSurfaceHeight(), - PIXEL_FORMAT_RGB_888, 0); - - ASSERT_TRUE(mSurfaceControl != NULL); - ASSERT_TRUE(mSurfaceControl->isValid()); - - SurfaceComposerClient::openGlobalTransaction(); - ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF)); - ASSERT_EQ(NO_ERROR, mSurfaceControl->show()); - SurfaceComposerClient::closeGlobalTransaction(); - - sp<ANativeWindow> window = mSurfaceControl->getSurface(); - mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, - window.get(), NULL); - } else { - EGLint pbufferAttribs[] = { - EGL_WIDTH, getSurfaceWidth(), - EGL_HEIGHT, getSurfaceHeight(), - EGL_NONE }; - - mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig, - pbufferAttribs); - } - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, mEglSurface); - - mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT, - getContextAttribs()); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_CONTEXT, mEglContext); - - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - EGLint w, h; - EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - RecordProperty("EglSurfaceWidth", w); - RecordProperty("EglSurfaceHeight", h); - - glViewport(0, 0, w, h); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - } - - virtual void TearDown() { - // Display the result - if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) { - eglSwapBuffers(mEglDisplay, mEglSurface); - sleep(mDisplaySecs); - } - - if (mComposerClient != NULL) { - mComposerClient->dispose(); - } - if (mEglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mEglContext); - } - if (mEglSurface != EGL_NO_SURFACE) { - eglDestroySurface(mEglDisplay, mEglSurface); - } - if (mEglDisplay != EGL_NO_DISPLAY) { - eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglTerminate(mEglDisplay); - } - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - const ::testing::TestInfo* const testInfo = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGV("End test: %s.%s", testInfo->test_case_name(), - testInfo->name()); - } - - virtual EGLint const* getConfigAttribs() { - static EGLint sDefaultConfigAttribs[] = { - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, 8, - EGL_NONE }; - - return sDefaultConfigAttribs; - } - - virtual EGLint const* getContextAttribs() { - static EGLint sDefaultContextAttribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE }; - - return sDefaultContextAttribs; - } - - virtual EGLint getSurfaceWidth() { - return 512; - } - - virtual EGLint getSurfaceHeight() { - return 512; - } - - ::testing::AssertionResult checkPixel(int x, int y, int r, - int g, int b, int a, int tolerance=2) { - GLubyte pixel[4]; - String8 msg; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - msg += String8::format("error reading pixel: %#x", err); - while ((err = glGetError()) != GL_NO_ERROR) { - msg += String8::format(", %#x", err); - } - return ::testing::AssertionFailure( - ::testing::Message(msg.string())); - } - if (r >= 0 && abs(r - int(pixel[0])) > tolerance) { - msg += String8::format("r(%d isn't %d)", pixel[0], r); - } - if (g >= 0 && abs(g - int(pixel[1])) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("g(%d isn't %d)", pixel[1], g); - } - if (b >= 0 && abs(b - int(pixel[2])) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("b(%d isn't %d)", pixel[2], b); - } - if (a >= 0 && abs(a - int(pixel[3])) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("a(%d isn't %d)", pixel[3], a); - } - if (!msg.isEmpty()) { - return ::testing::AssertionFailure( - ::testing::Message(msg.string())); - } else { - return ::testing::AssertionSuccess(); - } - } - - ::testing::AssertionResult assertRectEq(const Rect &r1, - const Rect &r2, int tolerance=1) { - - String8 msg; - - if (abs(r1.left - r2.left) > tolerance) { - msg += String8::format("left(%d isn't %d)", r1.left, r2.left); - } - if (abs(r1.top - r2.top) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("top(%d isn't %d)", r1.top, r2.top); - } - if (abs(r1.right - r2.right) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("right(%d isn't %d)", r1.right, r2.right); - } - if (abs(r1.bottom - r2.bottom) > tolerance) { - if (!msg.isEmpty()) { - msg += " "; - } - msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom); - } - if (!msg.isEmpty()) { - msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]", - r1.left, r1.top, r1.right, r1.bottom, - r2.left, r2.top, r2.right, r2.bottom); - fprintf(stderr, "assertRectEq: %s\n", msg.string()); - return ::testing::AssertionFailure( - ::testing::Message(msg.string())); - } else { - return ::testing::AssertionSuccess(); - } - } - - int mDisplaySecs; - sp<SurfaceComposerClient> mComposerClient; - sp<SurfaceControl> mSurfaceControl; - - EGLDisplay mEglDisplay; - EGLSurface mEglSurface; - EGLContext mEglContext; - EGLConfig mGlConfig; -}; - -static void loadShader(GLenum shaderType, const char* pSource, - GLuint* outShader) { - GLuint shader = glCreateShader(shaderType); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - if (shader) { - glShaderSource(shader, 1, &pSource, NULL); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glCompileShader(shader); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - GLint compiled = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - if (!compiled) { - GLint infoLen = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - if (infoLen) { - char* buf = (char*) malloc(infoLen); - if (buf) { - glGetShaderInfoLog(shader, infoLen, NULL, buf); - printf("Shader compile log:\n%s\n", buf); - free(buf); - FAIL(); - } - } else { - char* buf = (char*) malloc(0x1000); - if (buf) { - glGetShaderInfoLog(shader, 0x1000, NULL, buf); - printf("Shader compile log:\n%s\n", buf); - free(buf); - FAIL(); - } - } - glDeleteShader(shader); - shader = 0; - } - } - ASSERT_TRUE(shader != 0); - *outShader = shader; -} - -static void createProgram(const char* pVertexSource, - const char* pFragmentSource, GLuint* outPgm) { - GLuint vertexShader, fragmentShader; - { - SCOPED_TRACE("compiling vertex shader"); - ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource, - &vertexShader)); - } - { - SCOPED_TRACE("compiling fragment shader"); - ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource, - &fragmentShader)); - } - - GLuint program = glCreateProgram(); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - if (program) { - glAttachShader(program, vertexShader); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glAttachShader(program, fragmentShader); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glLinkProgram(program); - GLint linkStatus = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); - if (linkStatus != GL_TRUE) { - GLint bufLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); - if (bufLength) { - char* buf = (char*) malloc(bufLength); - if (buf) { - glGetProgramInfoLog(program, bufLength, NULL, buf); - printf("Program link log:\n%s\n", buf); - free(buf); - FAIL(); - } - } - glDeleteProgram(program); - program = 0; - } - } - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - ASSERT_TRUE(program != 0); - *outPgm = program; -} - -static int abs(int value) { - return value > 0 ? value : -value; -} - - -// XXX: Code above this point should live elsewhere - -class MultiTextureConsumerTest : public GLTest { -protected: - enum { TEX_ID = 123 }; - - virtual void SetUp() { - GLTest::SetUp(); - sp<BufferQueue> bq = new BufferQueue(); - mGlConsumer = new GLConsumer(bq, TEX_ID); - mSurface = new Surface(bq); - mANW = mSurface.get(); - - } - virtual void TearDown() { - GLTest::TearDown(); - } - virtual EGLint const* getContextAttribs() { - return NULL; - } - virtual EGLint const* getConfigAttribs() { - static EGLint sDefaultConfigAttribs[] = { - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_NONE }; - - return sDefaultConfigAttribs; - } - sp<GLConsumer> mGlConsumer; - sp<Surface> mSurface; - ANativeWindow* mANW; -}; - - -TEST_F(MultiTextureConsumerTest, EGLImageTargetWorks) { - ANativeWindow_Buffer buffer; - - ASSERT_EQ(native_window_set_usage(mANW, GRALLOC_USAGE_SW_WRITE_OFTEN), NO_ERROR); - ASSERT_EQ(native_window_set_buffers_format(mANW, HAL_PIXEL_FORMAT_RGBA_8888), NO_ERROR); - - glShadeModel(GL_FLAT); - glDisable(GL_DITHER); - glDisable(GL_CULL_FACE); - glViewport(0, 0, getSurfaceWidth(), getSurfaceHeight()); - glOrthof(0, getSurfaceWidth(), 0, getSurfaceHeight(), 0, 1); - glEnableClientState(GL_VERTEX_ARRAY); - glColor4f(1, 1, 1, 1); - - glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID); - glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - uint32_t texel = 0x80808080; - glBindTexture(GL_TEXTURE_2D, TEX_ID+1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texel); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, TEX_ID+1); - glEnable(GL_TEXTURE_2D); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID); - glEnable(GL_TEXTURE_EXTERNAL_OES); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - - glClear(GL_COLOR_BUFFER_BIT); - for (int i=0 ; i<8 ; i++) { - mSurface->lock(&buffer, NULL); - memset(buffer.bits, (i&7) * 0x20, buffer.stride * buffer.height * 4); - mSurface->unlockAndPost(); - - mGlConsumer->updateTexImage(); - - GLfloat vertices[][2] = { {i*16.0f, 0}, {(i+1)*16.0f, 0}, {(i+1)*16.0f, 16.0f}, {i*16.0f, 16.0f} }; - glVertexPointer(2, GL_FLOAT, 0, vertices); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - } - - for (int i=0 ; i<8 ; i++) { - EXPECT_TRUE(checkPixel(i*16 + 8, 8, i*16, i*16, i*16, i*16, 0)); - } -} - - - -class SurfaceTextureGLTest : public GLTest { -protected: - enum { TEX_ID = 123 }; - - virtual void SetUp() { - GLTest::SetUp(); - sp<BufferQueue> bq = new BufferQueue(); - mBQ = bq; - mST = new GLConsumer(bq, TEX_ID); - mSTC = new Surface(bq); - mANW = mSTC; - mTextureRenderer = new TextureRenderer(TEX_ID, mST); - ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp()); - mFW = new FrameWaiter; - mST->setFrameAvailableListener(mFW); - } - - virtual void TearDown() { - mANW.clear(); - mSTC.clear(); - mST.clear(); - GLTest::TearDown(); - } - - void drawTexture() { - mTextureRenderer->drawTexture(); - } - - class TextureRenderer: public RefBase { - public: - TextureRenderer(GLuint texName, const sp<GLConsumer>& st): - mTexName(texName), - mST(st) { - } - - void SetUp() { - const char vsrc[] = - "attribute vec4 vPosition;\n" - "varying vec2 texCoords;\n" - "uniform mat4 texMatrix;\n" - "void main() {\n" - " vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n" - " texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n" - " gl_Position = vPosition;\n" - "}\n"; - - const char fsrc[] = - "#extension GL_OES_EGL_image_external : require\n" - "precision mediump float;\n" - "uniform samplerExternalOES texSampler;\n" - "varying vec2 texCoords;\n" - "void main() {\n" - " gl_FragColor = texture2D(texSampler, texCoords);\n" - "}\n"; - - { - SCOPED_TRACE("creating shader program"); - ASSERT_NO_FATAL_FAILURE(createProgram(vsrc, fsrc, &mPgm)); - } - - mPositionHandle = glGetAttribLocation(mPgm, "vPosition"); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_NE(-1, mPositionHandle); - mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler"); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_NE(-1, mTexSamplerHandle); - mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix"); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_NE(-1, mTexMatrixHandle); - } - - // drawTexture draws the GLConsumer over the entire GL viewport. - void drawTexture() { - static const GLfloat triangleVertices[] = { - -1.0f, 1.0f, - -1.0f, -1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f, - }; - - glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, - triangleVertices); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glEnableVertexAttribArray(mPositionHandle); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - - glUseProgram(mPgm); - glUniform1i(mTexSamplerHandle, 0); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - - // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as - // they're setting the defautls for that target, but when hacking - // things to use GL_TEXTURE_2D they are needed to achieve the same - // behavior. - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, - GL_LINEAR); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, - GL_LINEAR); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, - GL_CLAMP_TO_EDGE); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, - GL_CLAMP_TO_EDGE); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - - GLfloat texMatrix[16]; - mST->getTransformMatrix(texMatrix); - glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - } - - GLuint mTexName; - sp<GLConsumer> mST; - GLuint mPgm; - GLint mPositionHandle; - GLint mTexSamplerHandle; - GLint mTexMatrixHandle; - }; - - class FrameWaiter : public GLConsumer::FrameAvailableListener { - public: - FrameWaiter(): - mPendingFrames(0) { - } - - void waitForFrame() { - Mutex::Autolock lock(mMutex); - while (mPendingFrames == 0) { - mCondition.wait(mMutex); - } - mPendingFrames--; - } - - virtual void onFrameAvailable() { - Mutex::Autolock lock(mMutex); - mPendingFrames++; - mCondition.signal(); - } - - int mPendingFrames; - Mutex mMutex; - Condition mCondition; - }; - - // Note that GLConsumer will lose the notifications - // onBuffersReleased and onFrameAvailable as there is currently - // no way to forward the events. This DisconnectWaiter will not let the - // disconnect finish until finishDisconnect() is called. It will - // also block until a disconnect is called - class DisconnectWaiter : public BnConsumerListener { - public: - DisconnectWaiter () : - mWaitForDisconnect(false), - mPendingFrames(0) { - } - - void waitForFrame() { - Mutex::Autolock lock(mMutex); - while (mPendingFrames == 0) { - mFrameCondition.wait(mMutex); - } - mPendingFrames--; - } - - virtual void onFrameAvailable() { - Mutex::Autolock lock(mMutex); - mPendingFrames++; - mFrameCondition.signal(); - } - - virtual void onBuffersReleased() { - Mutex::Autolock lock(mMutex); - while (!mWaitForDisconnect) { - mDisconnectCondition.wait(mMutex); - } - } - - void finishDisconnect() { - Mutex::Autolock lock(mMutex); - mWaitForDisconnect = true; - mDisconnectCondition.signal(); - } - - private: - Mutex mMutex; - - bool mWaitForDisconnect; - Condition mDisconnectCondition; - - int mPendingFrames; - Condition mFrameCondition; - }; - - sp<BufferQueue> mBQ; - sp<GLConsumer> mST; - sp<Surface> mSTC; - sp<ANativeWindow> mANW; - sp<TextureRenderer> mTextureRenderer; - sp<FrameWaiter> mFW; -}; - -// Fill a YV12 buffer with a multi-colored checkerboard pattern -void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) { - const int blockWidth = w > 16 ? w / 16 : 1; - const int blockHeight = h > 16 ? h / 16 : 1; - const int yuvTexOffsetY = 0; - int yuvTexStrideY = stride; - int yuvTexOffsetV = yuvTexStrideY * h; - int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; - int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; - int yuvTexStrideU = yuvTexStrideV; - for (int x = 0; x < w; x++) { - for (int y = 0; y < h; y++) { - int parityX = (x / blockWidth) & 1; - int parityY = (y / blockHeight) & 1; - unsigned char intensity = (parityX ^ parityY) ? 63 : 191; - buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity; - if (x < w / 2 && y < h / 2) { - buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity; - if (x * 2 < w / 2 && y * 2 < h / 2) { - buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] = - buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] = - buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] = - buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] = - intensity; - } - } - } - } -} - -// Fill a YV12 buffer with red outside a given rectangle and green inside it. -void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride, - const android_native_rect_t& rect) { - const int yuvTexOffsetY = 0; - int yuvTexStrideY = stride; - int yuvTexOffsetV = yuvTexStrideY * h; - int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; - int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; - int yuvTexStrideU = yuvTexStrideV; - for (int x = 0; x < w; x++) { - for (int y = 0; y < h; y++) { - bool inside = rect.left <= x && x < rect.right && - rect.top <= y && y < rect.bottom; - buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64; - if (x < w / 2 && y < h / 2) { - bool inside = rect.left <= 2*x && 2*x < rect.right && - rect.top <= 2*y && 2*y < rect.bottom; - buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16; - buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] = - inside ? 16 : 255; - } - } - } -} - -void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) { - const size_t PIXEL_SIZE = 4; - for (int x = 0; x < w; x++) { - for (int y = 0; y < h; y++) { - off_t offset = (y * stride + x) * PIXEL_SIZE; - for (int c = 0; c < 4; c++) { - int parityX = (x / (1 << (c+2))) & 1; - int parityY = (y / (1 << (c+2))) & 1; - buf[offset + c] = (parityX ^ parityY) ? 231 : 35; - } - } - } -} - -void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r, - uint8_t g, uint8_t b, uint8_t a) { - const size_t PIXEL_SIZE = 4; - for (int y = 0; y < h; y++) { - for (int x = 0; x < h; x++) { - off_t offset = (y * stride + x) * PIXEL_SIZE; - buf[offset + 0] = r; - buf[offset + 1] = g; - buf[offset + 2] = b; - buf[offset + 3] = a; - } - } -} - -// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern -// using the CPU. This assumes that the ANativeWindow is already configured to -// allow this to be done (e.g. the format is set to RGBA8). -// -// Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE(). -void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) { - android_native_buffer_t* anb; - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - uint8_t* img = NULL; - ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, - (void**)(&img))); - fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride()); - ASSERT_EQ(NO_ERROR, buf->unlock()); - ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer(), - -1)); -} - -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { - const int texWidth = 64; - const int texHeight = 66; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - ANativeWindowBuffer* anb; - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - // Fill the buffer with the a checkerboard pattern - uint8_t* img = NULL; - buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); - fillYV12Buffer(img, texWidth, texHeight, buf->getStride()); - buf->unlock(); - ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), - -1)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255, 3)); - EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255, 3)); - EXPECT_TRUE(checkPixel(63, 65, 0, 133, 0, 255, 3)); - EXPECT_TRUE(checkPixel( 0, 65, 255, 127, 255, 255, 3)); - - EXPECT_TRUE(checkPixel(22, 44, 255, 127, 255, 255, 3)); - EXPECT_TRUE(checkPixel(45, 52, 255, 127, 255, 255, 3)); - EXPECT_TRUE(checkPixel(52, 51, 98, 255, 73, 255, 3)); - EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255, 3)); - EXPECT_TRUE(checkPixel(31, 9, 107, 24, 87, 255, 3)); - EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255, 3)); - EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255, 3)); -} - -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferPow2) { - const int texWidth = 64; - const int texHeight = 64; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - ANativeWindowBuffer* anb; - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - // Fill the buffer with the a checkerboard pattern - uint8_t* img = NULL; - buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); - fillYV12Buffer(img, texWidth, texHeight, buf->getStride()); - buf->unlock(); - ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), - -1)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 0, 133, 0, 255)); - EXPECT_TRUE(checkPixel(63, 0, 255, 127, 255, 255)); - EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255)); - EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255)); - - EXPECT_TRUE(checkPixel(22, 19, 100, 255, 74, 255)); - EXPECT_TRUE(checkPixel(45, 11, 100, 255, 74, 255)); - EXPECT_TRUE(checkPixel(52, 12, 155, 0, 181, 255)); - EXPECT_TRUE(checkPixel( 7, 32, 150, 237, 170, 255)); - EXPECT_TRUE(checkPixel(31, 54, 0, 71, 117, 255)); - EXPECT_TRUE(checkPixel(29, 28, 0, 133, 0, 255)); - EXPECT_TRUE(checkPixel(36, 41, 100, 232, 255, 255)); -} - -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { - const int texWidth = 64; - const int texHeight = 66; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - android_native_rect_t crops[] = { - {4, 6, 22, 36}, - {0, 6, 22, 36}, - {4, 0, 22, 36}, - {4, 6, texWidth, 36}, - {4, 6, 22, texHeight}, - }; - - for (int i = 0; i < 5; i++) { - const android_native_rect_t& crop(crops[i]); - SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }", - crop.left, crop.top, crop.right, crop.bottom).string()); - - ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop)); - - ANativeWindowBuffer* anb; - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - uint8_t* img = NULL; - buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); - fillYV12BufferRect(img, texWidth, texHeight, buf->getStride(), crop); - buf->unlock(); - ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), - buf->getNativeBuffer(), -1)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, 64, 64); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(63, 0, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(63, 63, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel( 0, 63, 82, 255, 35, 255)); - - EXPECT_TRUE(checkPixel(25, 14, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(35, 31, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(57, 6, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel( 5, 42, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(32, 33, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(16, 26, 82, 255, 35, 255)); - EXPECT_TRUE(checkPixel(46, 51, 82, 255, 35, 255)); - } -} - -// This test is intended to catch synchronization bugs between the CPU-written -// and GPU-read buffers. -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) { - enum { texWidth = 16 }; - enum { texHeight = 16 }; - enum { numFrames = 1024 }; - - ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2)); - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_YV12)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_WRITE_OFTEN)); - - struct TestPixel { - int x; - int y; - }; - const TestPixel testPixels[] = { - { 4, 11 }, - { 12, 14 }, - { 7, 2 }, - }; - enum {numTestPixels = sizeof(testPixels) / sizeof(testPixels[0])}; - - class ProducerThread : public Thread { - public: - ProducerThread(const sp<ANativeWindow>& anw, - const TestPixel* testPixels): - mANW(anw), - mTestPixels(testPixels) { - } - - virtual ~ProducerThread() { - } - - virtual bool threadLoop() { - for (int i = 0; i < numFrames; i++) { - ANativeWindowBuffer* anb; - if (native_window_dequeue_buffer_and_wait(mANW.get(), - &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - const int yuvTexOffsetY = 0; - int stride = buf->getStride(); - int yuvTexStrideY = stride; - int yuvTexOffsetV = yuvTexStrideY * texHeight; - int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; - int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * texHeight/2; - int yuvTexStrideU = yuvTexStrideV; - - uint8_t* img = NULL; - buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); - - // Gray out all the test pixels first, so we're more likely to - // see a failure if GL is still texturing from the buffer we - // just dequeued. - for (int j = 0; j < numTestPixels; j++) { - int x = mTestPixels[j].x; - int y = mTestPixels[j].y; - uint8_t value = 128; - img[y*stride + x] = value; - } - - // Fill the buffer with gray. - for (int y = 0; y < texHeight; y++) { - for (int x = 0; x < texWidth; x++) { - img[yuvTexOffsetY + y*yuvTexStrideY + x] = 128; - img[yuvTexOffsetU + (y/2)*yuvTexStrideU + x/2] = 128; - img[yuvTexOffsetV + (y/2)*yuvTexStrideV + x/2] = 128; - } - } - - // Set the test pixels to either white or black. - for (int j = 0; j < numTestPixels; j++) { - int x = mTestPixels[j].x; - int y = mTestPixels[j].y; - uint8_t value = 0; - if (j == (i % numTestPixels)) { - value = 255; - } - img[y*stride + x] = value; - } - - buf->unlock(); - if (mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), -1) - != NO_ERROR) { - return false; - } - } - return false; - } - - sp<ANativeWindow> mANW; - const TestPixel* mTestPixels; - }; - - sp<Thread> pt(new ProducerThread(mANW, testPixels)); - pt->run(); - - glViewport(0, 0, texWidth, texHeight); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - // We wait for the first two frames up front so that the producer will be - // likely to dequeue the buffer that's currently being textured from. - mFW->waitForFrame(); - mFW->waitForFrame(); - - for (int i = 0; i < numFrames; i++) { - SCOPED_TRACE(String8::format("frame %d", i).string()); - - // We must wait for each frame to come in because if we ever do an - // updateTexImage call that doesn't consume a newly available buffer - // then the producer and consumer will get out of sync, which will cause - // a deadlock. - if (i > 1) { - mFW->waitForFrame(); - } - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - drawTexture(); - - for (int j = 0; j < numTestPixels; j++) { - int x = testPixels[j].x; - int y = testPixels[j].y; - uint8_t value = 0; - if (j == (i % numTestPixels)) { - // We must y-invert the texture coords - EXPECT_TRUE(checkPixel(x, texHeight-y-1, 255, 255, 255, 255)); - } else { - // We must y-invert the texture coords - EXPECT_TRUE(checkPixel(x, texHeight-y-1, 0, 0, 0, 255)); - } - } - } - - pt->requestExitAndWait(); -} - -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) { - const int texWidth = 64; - const int texHeight = 66; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); - EXPECT_TRUE(checkPixel(63, 0, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel(63, 65, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35)); - - EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(23, 65, 231, 35, 231, 35)); - EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35)); - EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35)); - EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(37, 34, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231)); - EXPECT_TRUE(checkPixel(37, 47, 231, 35, 231, 231)); - EXPECT_TRUE(checkPixel(25, 38, 35, 35, 35, 35)); - EXPECT_TRUE(checkPixel(49, 6, 35, 231, 35, 35)); - EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(27, 26, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel(10, 6, 35, 35, 231, 231)); - EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(55, 28, 35, 35, 231, 35)); - EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231)); -} - -TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { - const int texWidth = 64; - const int texHeight = 64; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel(63, 0, 35, 35, 35, 35)); - EXPECT_TRUE(checkPixel(63, 63, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel( 0, 63, 35, 35, 35, 35)); - - EXPECT_TRUE(checkPixel(12, 46, 231, 231, 231, 35)); - EXPECT_TRUE(checkPixel(16, 1, 231, 231, 35, 231)); - EXPECT_TRUE(checkPixel(21, 12, 231, 35, 35, 231)); - EXPECT_TRUE(checkPixel(26, 51, 231, 35, 231, 35)); - EXPECT_TRUE(checkPixel( 5, 32, 35, 231, 231, 35)); - EXPECT_TRUE(checkPixel(13, 8, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(46, 3, 35, 35, 231, 35)); - EXPECT_TRUE(checkPixel(30, 33, 35, 35, 35, 35)); - EXPECT_TRUE(checkPixel( 6, 52, 231, 231, 35, 35)); - EXPECT_TRUE(checkPixel(55, 33, 35, 231, 35, 231)); - EXPECT_TRUE(checkPixel(16, 29, 35, 35, 231, 231)); - EXPECT_TRUE(checkPixel( 1, 30, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(41, 37, 35, 35, 231, 231)); - EXPECT_TRUE(checkPixel(46, 29, 231, 231, 35, 35)); - EXPECT_TRUE(checkPixel(15, 25, 35, 231, 35, 231)); - EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35)); -} - -// Tests if GLConsumer and BufferQueue are robust enough -// to handle a special case where updateTexImage is called -// in the middle of disconnect. This ordering is enforced -// by blocking in the disconnect callback. -TEST_F(SurfaceTextureGLTest, DisconnectStressTest) { - - class ProducerThread : public Thread { - public: - ProducerThread(const sp<ANativeWindow>& anw): - mANW(anw) { - } - - virtual ~ProducerThread() { - } - - virtual bool threadLoop() { - ANativeWindowBuffer* anb; - - native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_EGL); - - for (int numFrames =0 ; numFrames < 2; numFrames ++) { - - if (native_window_dequeue_buffer_and_wait(mANW.get(), - &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - if (mANW->queueBuffer(mANW.get(), anb, -1) - != NO_ERROR) { - return false; - } - } - - native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL); - - return false; - } - - private: - sp<ANativeWindow> mANW; - }; - - sp<DisconnectWaiter> dw(new DisconnectWaiter()); - mBQ->consumerConnect(dw, false); - - - sp<Thread> pt(new ProducerThread(mANW)); - pt->run(); - - // eat a frame so GLConsumer will own an at least one slot - dw->waitForFrame(); - EXPECT_EQ(OK,mST->updateTexImage()); - - dw->waitForFrame(); - // Could fail here as GLConsumer thinks it still owns the slot - // but bufferQueue has released all slots - EXPECT_EQ(OK,mST->updateTexImage()); - - dw->finishDisconnect(); -} - - -// This test ensures that the GLConsumer clears the mCurrentTexture -// when it is disconnected and reconnected. Otherwise it will -// attempt to release a buffer that it does not owned -TEST_F(SurfaceTextureGLTest, DisconnectClearsCurrentTexture) { - ASSERT_EQ(OK, native_window_api_connect(mANW.get(), - NATIVE_WINDOW_API_EGL)); - - ANativeWindowBuffer *anb; - - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - - EXPECT_EQ(OK,mST->updateTexImage()); - EXPECT_EQ(OK,mST->updateTexImage()); - - ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), - NATIVE_WINDOW_API_EGL)); - ASSERT_EQ(OK, native_window_api_connect(mANW.get(), - NATIVE_WINDOW_API_EGL)); - - EXPECT_EQ(OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - - // Will fail here if mCurrentTexture is not cleared properly - mFW->waitForFrame(); - EXPECT_EQ(OK,mST->updateTexImage()); - - ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), - NATIVE_WINDOW_API_EGL)); -} - -TEST_F(SurfaceTextureGLTest, ScaleToWindowMode) { - ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(), - NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW)); - - // The producer image size - ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512)); - - // The consumer image size (16 x 9) ratio - mST->setDefaultBufferSize(1280, 720); - - ASSERT_EQ(OK, native_window_api_connect(mANW.get(), - NATIVE_WINDOW_API_CPU)); - - ANativeWindowBuffer *anb; - - android_native_rect_t odd = {23, 78, 123, 477}; - ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &odd)); - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - mFW->waitForFrame(); - EXPECT_EQ(OK, mST->updateTexImage()); - Rect r = mST->getCurrentCrop(); - assertRectEq(Rect(23, 78, 123, 477), r); - - ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(), - NATIVE_WINDOW_API_CPU)); -} - -// This test ensures the scaling mode does the right thing -// ie NATIVE_WINDOW_SCALING_MODE_CROP should crop -// the image such that it has the same aspect ratio as the -// default buffer size -TEST_F(SurfaceTextureGLTest, CroppedScalingMode) { - ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(), - NATIVE_WINDOW_SCALING_MODE_SCALE_CROP)); - - // The producer image size - ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512)); - - // The consumer image size (16 x 9) ratio - mST->setDefaultBufferSize(1280, 720); - - native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU); - - ANativeWindowBuffer *anb; - - // The crop is in the shape of (320, 180) === 16 x 9 - android_native_rect_t standard = {10, 20, 330, 200}; - ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &standard)); - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - mFW->waitForFrame(); - EXPECT_EQ(OK, mST->updateTexImage()); - Rect r = mST->getCurrentCrop(); - // crop should be the same as crop (same aspect ratio) - assertRectEq(Rect(10, 20, 330, 200), r); - - // make this wider then desired aspect 239 x 100 (2.39:1) - android_native_rect_t wide = {20, 30, 259, 130}; - ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &wide)); - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - mFW->waitForFrame(); - EXPECT_EQ(OK, mST->updateTexImage()); - r = mST->getCurrentCrop(); - // crop should be the same height, but have cropped left and right borders - // offset is 30.6 px L+, R- - assertRectEq(Rect(51, 30, 228, 130), r); - - // This image is taller then desired aspect 400 x 300 (4:3) - android_native_rect_t narrow = {0, 0, 400, 300}; - ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &narrow)); - EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb)); - EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1)); - mFW->waitForFrame(); - EXPECT_EQ(OK, mST->updateTexImage()); - r = mST->getCurrentCrop(); - // crop should be the same width, but have cropped top and bottom borders - // offset is 37.5 px - assertRectEq(Rect(0, 37, 400, 262), r); - - native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); -} - -TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) { - class ProducerThread : public Thread { - public: - ProducerThread(const sp<ANativeWindow>& anw): - mANW(anw), - mDequeueError(NO_ERROR) { - } - - virtual ~ProducerThread() { - } - - virtual bool threadLoop() { - Mutex::Autolock lock(mMutex); - ANativeWindowBuffer* anb; - - // Frame 1 - if (native_window_dequeue_buffer_and_wait(mANW.get(), - &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - if (mANW->queueBuffer(mANW.get(), anb, -1) - != NO_ERROR) { - return false; - } - - // Frame 2 - if (native_window_dequeue_buffer_and_wait(mANW.get(), - &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - if (mANW->queueBuffer(mANW.get(), anb, -1) - != NO_ERROR) { - return false; - } - - // Frame 3 - error expected - mDequeueError = native_window_dequeue_buffer_and_wait(mANW.get(), - &anb); - return false; - } - - status_t getDequeueError() { - Mutex::Autolock lock(mMutex); - return mDequeueError; - } - - private: - sp<ANativeWindow> mANW; - status_t mDequeueError; - Mutex mMutex; - }; - - ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2)); - - sp<Thread> pt(new ProducerThread(mANW)); - pt->run(); - - mFW->waitForFrame(); - mFW->waitForFrame(); - - // Sleep for 100ms to allow the producer thread's dequeueBuffer call to - // block waiting for a buffer to become available. - usleep(100000); - - mST->abandon(); - - pt->requestExitAndWait(); - ASSERT_EQ(NO_INIT, - reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError()); -} - -TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) { - int texHeight = 16; - ANativeWindowBuffer* anb; - - GLint maxTextureSize; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); - - // make sure it works with small textures - mST->setDefaultBufferSize(16, texHeight); - EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - EXPECT_EQ(16, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); - EXPECT_EQ(NO_ERROR, mST->updateTexImage()); - - // make sure it works with GL_MAX_TEXTURE_SIZE - mST->setDefaultBufferSize(maxTextureSize, texHeight); - EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - EXPECT_EQ(maxTextureSize, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); - EXPECT_EQ(NO_ERROR, mST->updateTexImage()); - - // make sure it fails with GL_MAX_TEXTURE_SIZE+1 - mST->setDefaultBufferSize(maxTextureSize+1, texHeight); - EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - EXPECT_EQ(maxTextureSize+1, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1)); - ASSERT_NE(NO_ERROR, mST->updateTexImage()); -} - -/* - * This test fixture is for testing GL -> GL texture streaming. It creates an - * EGLSurface and an EGLContext for the image producer to use. - */ -class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest { -protected: - SurfaceTextureGLToGLTest(): - mProducerEglSurface(EGL_NO_SURFACE), - mProducerEglContext(EGL_NO_CONTEXT) { - } - - virtual void SetUp() { - SurfaceTextureGLTest::SetUp(); - - mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, - mANW.get(), NULL); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); - - mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig, - EGL_NO_CONTEXT, getContextAttribs()); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); - } - - virtual void TearDown() { - if (mProducerEglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mProducerEglContext); - } - if (mProducerEglSurface != EGL_NO_SURFACE) { - eglDestroySurface(mEglDisplay, mProducerEglSurface); - } - SurfaceTextureGLTest::TearDown(); - } - - EGLSurface mProducerEglSurface; - EGLContext mProducerEglContext; -}; - -TEST_F(SurfaceTextureGLToGLTest, TransformHintGetsRespected) { - const uint32_t texWidth = 32; - const uint32_t texHeight = 64; - - mST->setDefaultBufferSize(texWidth, texHeight); - mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); - - // This test requires 3 buffers to avoid deadlock because we're - // both producer and consumer, and only using one thread. - mST->setDefaultMaxBufferCount(3); - - // Do the producer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Start a buffer with our chosen size and transform hint moving - // through the system. - glClear(GL_COLOR_BUFFER_BIT); // give the driver something to do - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - mST->updateTexImage(); // consume it - // Swap again. - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - mST->updateTexImage(); - - // The current buffer should either show the effects of the transform - // hint (in the form of an inverse transform), or show that the - // transform hint has been ignored. - sp<GraphicBuffer> buf = mST->getCurrentBuffer(); - if (mST->getCurrentTransform() == NATIVE_WINDOW_TRANSFORM_ROT_270) { - ASSERT_EQ(texWidth, buf->getHeight()); - ASSERT_EQ(texHeight, buf->getWidth()); - } else { - ASSERT_EQ(texWidth, buf->getWidth()); - ASSERT_EQ(texHeight, buf->getHeight()); - } - - // Reset the transform hint and confirm that it takes. - mST->setTransformHint(0); - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - mST->updateTexImage(); - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - mST->updateTexImage(); - - buf = mST->getCurrentBuffer(); - ASSERT_EQ((uint32_t) 0, mST->getCurrentTransform()); - ASSERT_EQ(texWidth, buf->getWidth()); - ASSERT_EQ(texHeight, buf->getHeight()); -} - -TEST_F(SurfaceTextureGLToGLTest, TexturingFromGLFilledRGBABufferPow2) { - const int texWidth = 64; - const int texHeight = 64; - - mST->setDefaultBufferSize(texWidth, texHeight); - - // This test requires 3 buffers to complete run on a single thread. - mST->setDefaultMaxBufferCount(3); - - // Do the producer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // This is needed to ensure we pick up a buffer of the correct size. - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - glClearColor(0.6, 0.6, 0.6, 0.6); - glClear(GL_COLOR_BUFFER_BIT); - - glEnable(GL_SCISSOR_TEST); - glScissor(4, 4, 4, 4); - glClearColor(1.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - glScissor(24, 48, 4, 4); - glClearColor(0.0, 1.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - glScissor(37, 17, 4, 4); - glClearColor(0.0, 0.0, 1.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - // Do the consumer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - glDisable(GL_SCISSOR_TEST); - - // Skip the first frame, which was empty - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153)); - - EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255)); - EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255)); - EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255)); - EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153)); -} - -TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceUnrefsBuffers) { - sp<GraphicBuffer> buffers[2]; - - // This test requires async mode to run on a single thread. - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - for (int i = 0; i < 2; i++) { - // Produce a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - // Consume a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mFW->waitForFrame(); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - buffers[i] = mST->getCurrentBuffer(); - } - - // Destroy the GL texture object to release its ref on buffers[2]. - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); - - // Destroy the EGLSurface - EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mProducerEglSurface = EGL_NO_SURFACE; - - // This test should have the only reference to buffer 0. - EXPECT_EQ(1, buffers[0]->getStrongCount()); - - // The GLConsumer should hold a single reference to buffer 1 in its - // mCurrentBuffer member. All of the references in the slots should have - // been released. - EXPECT_EQ(2, buffers[1]->getStrongCount()); -} - -TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) { - sp<GraphicBuffer> buffers[3]; - - // This test requires async mode to run on a single thread. - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - for (int i = 0; i < 3; i++) { - // Produce a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - glClear(GL_COLOR_BUFFER_BIT); - EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Consume a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mFW->waitForFrame(); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - buffers[i] = mST->getCurrentBuffer(); - } - - // Abandon the GLConsumer, releasing the ref that the GLConsumer has - // on buffers[2]. - mST->abandon(); - - // Destroy the GL texture object to release its ref on buffers[2]. - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); - - // Destroy the EGLSurface. - EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mProducerEglSurface = EGL_NO_SURFACE; - - EXPECT_EQ(1, buffers[0]->getStrongCount()); - EXPECT_EQ(1, buffers[1]->getStrongCount()); - - // Depending on how lazily the GL driver dequeues buffers, we may end up - // with either two or three total buffers. If there are three, make sure - // the last one was properly down-ref'd. - if (buffers[2] != buffers[0]) { - EXPECT_EQ(1, buffers[2]->getStrongCount()); - } -} - -TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentBeforeConsumerDeathUnrefsBuffers) { - sp<GraphicBuffer> buffer; - - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - - // Produce a frame - glClear(GL_COLOR_BUFFER_BIT); - EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Destroy the EGLSurface. - EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mProducerEglSurface = EGL_NO_SURFACE; - mSTC.clear(); - mANW.clear(); - mTextureRenderer.clear(); - - // Consume a frame - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - buffer = mST->getCurrentBuffer(); - - // Destroy the GL texture object to release its ref - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); - - // make un-current, all references to buffer should be gone - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT)); - - // Destroy consumer - mST.clear(); - - EXPECT_EQ(1, buffer->getStrongCount()); -} - -TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentAfterConsumerDeathUnrefsBuffers) { - sp<GraphicBuffer> buffer; - - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - - // Produce a frame - glClear(GL_COLOR_BUFFER_BIT); - EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Destroy the EGLSurface. - EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mProducerEglSurface = EGL_NO_SURFACE; - mSTC.clear(); - mANW.clear(); - mTextureRenderer.clear(); - - // Consume a frame - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - buffer = mST->getCurrentBuffer(); - - // Destroy the GL texture object to release its ref - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); - - // Destroy consumer - mST.clear(); - - // make un-current, all references to buffer should be gone - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT)); - - EXPECT_EQ(1, buffer->getStrongCount()); -} - -TEST_F(SurfaceTextureGLToGLTest, TexturingFromUserSizedGLFilledBuffer) { - enum { texWidth = 64 }; - enum { texHeight = 64 }; - - // This test requires 3 buffers to complete run on a single thread. - mST->setDefaultMaxBufferCount(3); - - // Set the user buffer size. - native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight); - - // Do the producer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // This is needed to ensure we pick up a buffer of the correct size. - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - glClearColor(0.6, 0.6, 0.6, 0.6); - glClear(GL_COLOR_BUFFER_BIT); - - glEnable(GL_SCISSOR_TEST); - glScissor(4, 4, 1, 1); - glClearColor(1.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - // Do the consumer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - glDisable(GL_SCISSOR_TEST); - - // Skip the first frame, which was empty - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153)); - - EXPECT_TRUE(checkPixel( 4, 4, 255, 0, 0, 255)); - EXPECT_TRUE(checkPixel( 5, 5, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 3, 3, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(45, 52, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(12, 36, 153, 153, 153, 153)); -} - -TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedUserSizedGLFilledBuffer) { - enum { texWidth = 64 }; - enum { texHeight = 16 }; - - // This test requires 3 buffers to complete run on a single thread. - mST->setDefaultMaxBufferCount(3); - - // Set the transform hint. - mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); - - // Set the user buffer size. - native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight); - - // Do the producer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // This is needed to ensure we pick up a buffer of the correct size and the - // new rotation hint. - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - glClearColor(0.6, 0.6, 0.6, 0.6); - glClear(GL_COLOR_BUFFER_BIT); - - glEnable(GL_SCISSOR_TEST); - glScissor(24, 4, 1, 1); - glClearColor(1.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - // Do the consumer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - glDisable(GL_SCISSOR_TEST); - - // Skip the first frame, which was empty - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153)); - - EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255)); - EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153)); -} - -TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedGLFilledBuffer) { - enum { texWidth = 64 }; - enum { texHeight = 16 }; - - // This test requires 3 buffers to complete run on a single thread. - mST->setDefaultMaxBufferCount(3); - - // Set the transform hint. - mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90); - - // Set the default buffer size. - mST->setDefaultBufferSize(texWidth, texHeight); - - // Do the producer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // This is needed to ensure we pick up a buffer of the correct size and the - // new rotation hint. - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - glClearColor(0.6, 0.6, 0.6, 0.6); - glClear(GL_COLOR_BUFFER_BIT); - - glEnable(GL_SCISSOR_TEST); - glScissor(24, 4, 1, 1); - glClearColor(1.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - eglSwapBuffers(mEglDisplay, mProducerEglSurface); - - // Do the consumer side of things - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - glDisable(GL_SCISSOR_TEST); - - // Skip the first frame, which was empty - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, texWidth, texHeight); - drawTexture(); - - EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153)); - - EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255)); - EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153)); - EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153)); -} - -/* - * This test fixture is for testing GL -> GL texture streaming from one thread - * to another. It contains functionality to create a producer thread that will - * perform GL rendering to an ANativeWindow that feeds frames to a - * GLConsumer. Additionally it supports interlocking the producer and - * consumer threads so that a specific sequence of calls can be - * deterministically created by the test. - * - * The intended usage is as follows: - * - * TEST_F(...) { - * class PT : public ProducerThread { - * virtual void render() { - * ... - * swapBuffers(); - * } - * }; - * - * runProducerThread(new PT()); - * - * // The order of these calls will vary from test to test and may include - * // multiple frames and additional operations (e.g. GL rendering from the - * // texture). - * fc->waitForFrame(); - * mST->updateTexImage(); - * fc->finishFrame(); - * } - * - */ -class SurfaceTextureGLThreadToGLTest : public SurfaceTextureGLToGLTest { -protected: - - // ProducerThread is an abstract base class to simplify the creation of - // OpenGL ES frame producer threads. - class ProducerThread : public Thread { - public: - virtual ~ProducerThread() { - } - - void setEglObjects(EGLDisplay producerEglDisplay, - EGLSurface producerEglSurface, - EGLContext producerEglContext) { - mProducerEglDisplay = producerEglDisplay; - mProducerEglSurface = producerEglSurface; - mProducerEglContext = producerEglContext; - } - - virtual bool threadLoop() { - eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface, - mProducerEglSurface, mProducerEglContext); - render(); - eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - return false; - } - - protected: - virtual void render() = 0; - - void swapBuffers() { - eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface); - } - - EGLDisplay mProducerEglDisplay; - EGLSurface mProducerEglSurface; - EGLContext mProducerEglContext; - }; - - // FrameCondition is a utility class for interlocking between the producer - // and consumer threads. The FrameCondition object should be created and - // destroyed in the consumer thread only. The consumer thread should set - // the FrameCondition as the FrameAvailableListener of the GLConsumer, - // and should call both waitForFrame and finishFrame once for each expected - // frame. - // - // This interlocking relies on the fact that onFrameAvailable gets called - // synchronously from GLConsumer::queueBuffer. - class FrameCondition : public GLConsumer::FrameAvailableListener { - public: - FrameCondition(): - mFrameAvailable(false), - mFrameFinished(false) { - } - - // waitForFrame waits for the next frame to arrive. This should be - // called from the consumer thread once for every frame expected by the - // test. - void waitForFrame() { - Mutex::Autolock lock(mMutex); - ALOGV("+waitForFrame"); - while (!mFrameAvailable) { - mFrameAvailableCondition.wait(mMutex); - } - mFrameAvailable = false; - ALOGV("-waitForFrame"); - } - - // Allow the producer to return from its swapBuffers call and continue - // on to produce the next frame. This should be called by the consumer - // thread once for every frame expected by the test. - void finishFrame() { - Mutex::Autolock lock(mMutex); - ALOGV("+finishFrame"); - mFrameFinished = true; - mFrameFinishCondition.signal(); - ALOGV("-finishFrame"); - } - - // This should be called by GLConsumer on the producer thread. - virtual void onFrameAvailable() { - Mutex::Autolock lock(mMutex); - ALOGV("+onFrameAvailable"); - mFrameAvailable = true; - mFrameAvailableCondition.signal(); - while (!mFrameFinished) { - mFrameFinishCondition.wait(mMutex); - } - mFrameFinished = false; - ALOGV("-onFrameAvailable"); - } - - protected: - bool mFrameAvailable; - bool mFrameFinished; - - Mutex mMutex; - Condition mFrameAvailableCondition; - Condition mFrameFinishCondition; - }; - - virtual void SetUp() { - SurfaceTextureGLToGLTest::SetUp(); - mFC = new FrameCondition(); - mST->setFrameAvailableListener(mFC); - } - - virtual void TearDown() { - if (mProducerThread != NULL) { - mProducerThread->requestExitAndWait(); - } - mProducerThread.clear(); - mFC.clear(); - SurfaceTextureGLToGLTest::TearDown(); - } - - void runProducerThread(const sp<ProducerThread> producerThread) { - ASSERT_TRUE(mProducerThread == NULL); - mProducerThread = producerThread; - producerThread->setEglObjects(mEglDisplay, mProducerEglSurface, - mProducerEglContext); - producerThread->run(); - } - - sp<ProducerThread> mProducerThread; - sp<FrameCondition> mFC; -}; - -TEST_F(SurfaceTextureGLThreadToGLTest, - UpdateTexImageBeforeFrameFinishedCompletes) { - class PT : public ProducerThread { - virtual void render() { - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - swapBuffers(); - } - }; - - runProducerThread(new PT()); - - mFC->waitForFrame(); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - mFC->finishFrame(); - - // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! -} - -TEST_F(SurfaceTextureGLThreadToGLTest, - UpdateTexImageAfterFrameFinishedCompletes) { - class PT : public ProducerThread { - virtual void render() { - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - swapBuffers(); - } - }; - - runProducerThread(new PT()); - - mFC->waitForFrame(); - mFC->finishFrame(); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! -} - -TEST_F(SurfaceTextureGLThreadToGLTest, - RepeatedUpdateTexImageBeforeFrameFinishedCompletes) { - enum { NUM_ITERATIONS = 1024 }; - - class PT : public ProducerThread { - virtual void render() { - for (int i = 0; i < NUM_ITERATIONS; i++) { - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - ALOGV("+swapBuffers"); - swapBuffers(); - ALOGV("-swapBuffers"); - } - } - }; - - runProducerThread(new PT()); - - for (int i = 0; i < NUM_ITERATIONS; i++) { - mFC->waitForFrame(); - ALOGV("+updateTexImage"); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ALOGV("-updateTexImage"); - mFC->finishFrame(); - - // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! - } -} - -TEST_F(SurfaceTextureGLThreadToGLTest, - RepeatedUpdateTexImageAfterFrameFinishedCompletes) { - enum { NUM_ITERATIONS = 1024 }; - - class PT : public ProducerThread { - virtual void render() { - for (int i = 0; i < NUM_ITERATIONS; i++) { - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - ALOGV("+swapBuffers"); - swapBuffers(); - ALOGV("-swapBuffers"); - } - } - }; - - runProducerThread(new PT()); - - for (int i = 0; i < NUM_ITERATIONS; i++) { - mFC->waitForFrame(); - mFC->finishFrame(); - ALOGV("+updateTexImage"); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ALOGV("-updateTexImage"); - - // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! - } -} - -// XXX: This test is disabled because it is currently hanging on some devices. -TEST_F(SurfaceTextureGLThreadToGLTest, - DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) { - enum { NUM_ITERATIONS = 64 }; - - class PT : public ProducerThread { - virtual void render() { - for (int i = 0; i < NUM_ITERATIONS; i++) { - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - ALOGV("+swapBuffers"); - swapBuffers(); - ALOGV("-swapBuffers"); - } - } - }; - - ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2)); - - runProducerThread(new PT()); - - // Allow three frames to be rendered and queued before starting the - // rendering in this thread. For the latter two frames we don't call - // updateTexImage so the next dequeue from the producer thread will block - // waiting for a frame to become available. - mFC->waitForFrame(); - mFC->finishFrame(); - - // We must call updateTexImage to consume the first frame so that the - // SurfaceTexture is able to reduce the buffer count to 2. This is because - // the GL driver may dequeue a buffer when the EGLSurface is created, and - // that happens before we call setDefaultMaxBufferCount. It's possible that the - // driver does not dequeue a buffer at EGLSurface creation time, so we - // cannot rely on this to cause the second dequeueBuffer call to block. - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - mFC->waitForFrame(); - mFC->finishFrame(); - mFC->waitForFrame(); - mFC->finishFrame(); - - // Sleep for 100ms to allow the producer thread's dequeueBuffer call to - // block waiting for a buffer to become available. - usleep(100000); - - // Render and present a number of images. This thread should not be blocked - // by the fact that the producer thread is blocking in dequeue. - for (int i = 0; i < NUM_ITERATIONS; i++) { - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, mEglSurface); - } - - // Consume the two pending buffers to unblock the producer thread. - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - // Consume the remaining buffers from the producer thread. - for (int i = 0; i < NUM_ITERATIONS-3; i++) { - mFC->waitForFrame(); - mFC->finishFrame(); - ALOGV("+updateTexImage"); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - ALOGV("-updateTexImage"); - } -} - -class SurfaceTextureFBOTest : public SurfaceTextureGLTest { -protected: - - virtual void SetUp() { - SurfaceTextureGLTest::SetUp(); - - glGenFramebuffers(1, &mFbo); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - - glGenTextures(1, &mFboTex); - glBindTexture(GL_TEXTURE_2D, mFboTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(), - getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - - glBindFramebuffer(GL_FRAMEBUFFER, mFbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, mFboTex, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - } - - virtual void TearDown() { - SurfaceTextureGLTest::TearDown(); - - glDeleteTextures(1, &mFboTex); - glDeleteFramebuffers(1, &mFbo); - } - - GLuint mFbo; - GLuint mFboTex; -}; - -// This test is intended to verify that proper synchronization is done when -// rendering into an FBO. -TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) { - const int texWidth = 64; - const int texHeight = 64; - - ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), - texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888)); - ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), - GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); - - android_native_buffer_t* anb; - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - - // Fill the buffer with green - uint8_t* img = NULL; - buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); - fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255, - 0, 255); - buf->unlock(); - ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), - -1)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - glBindFramebuffer(GL_FRAMEBUFFER, mFbo); - drawTexture(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - for (int i = 0; i < 4; i++) { - SCOPED_TRACE(String8::format("frame %d", i).string()); - - ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(), - &anb)); - ASSERT_TRUE(anb != NULL); - - buf = new GraphicBuffer(anb, false); - - // Fill the buffer with red - ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, - (void**)(&img))); - fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0, - 0, 255); - ASSERT_EQ(NO_ERROR, buf->unlock()); - ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), - buf->getNativeBuffer(), -1)); - - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - - drawTexture(); - - EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255)); - } - - glBindFramebuffer(GL_FRAMEBUFFER, mFbo); - - EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255)); -} - -class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest { -protected: - enum { SECOND_TEX_ID = 123 }; - enum { THIRD_TEX_ID = 456 }; - - SurfaceTextureMultiContextGLTest(): - mSecondEglContext(EGL_NO_CONTEXT) { - } - - virtual void SetUp() { - SurfaceTextureGLTest::SetUp(); - - // Set up the secondary context and texture renderer. - mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig, - EGL_NO_CONTEXT, getContextAttribs()); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext); - - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST); - ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp()); - - // Set up the tertiary context and texture renderer. - mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig, - EGL_NO_CONTEXT, getContextAttribs()); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext); - - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mThirdEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST); - ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp()); - - // Switch back to the primary context to start the tests. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - } - - virtual void TearDown() { - if (mThirdEglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mThirdEglContext); - } - if (mSecondEglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mSecondEglContext); - } - SurfaceTextureGLTest::TearDown(); - } - - EGLContext mSecondEglContext; - sp<TextureRenderer> mSecondTextureRenderer; - - EGLContext mThirdEglContext; - sp<TextureRenderer> mThirdTextureRenderer; -}; - -TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Attempt to latch the texture on the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Check that the GL texture was deleted. - EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - DetachFromContextSucceedsAfterProducerDisconnect) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); - ASSERT_EQ(OK, mST->detachFromContext()); - - // Check that the GL texture was deleted. - EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Attempt to detach from the primary context. - mST->abandon(); - ASSERT_EQ(NO_INIT, mST->detachFromContext()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attempt to detach from the primary context again. - ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Make there be no current display. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Attempt to detach from the primary context. - ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Make current context be incorrect. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Attempt to detach from the primary context. - ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attempt to latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Verify that the texture object was created and bound. - GLint texBinding = -1; - glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); - EXPECT_EQ(SECOND_TEX_ID, texBinding); - - // Try to use the texture from the secondary context. - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, 1, 1); - mSecondTextureRenderer->drawTexture(); - ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - AttachToContextSucceedsAfterProducerDisconnect) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Verify that the texture object was created and bound. - GLint texBinding = -1; - glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); - EXPECT_EQ(SECOND_TEX_ID, texBinding); - - // Try to use the texture from the secondary context. - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, 1, 1); - mSecondTextureRenderer->drawTexture(); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - AttachToContextSucceedsBeforeUpdateTexImage) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Detach from the primary context. - native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU); - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Verify that the texture object was created and bound. - GLint texBinding = -1; - glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); - EXPECT_EQ(SECOND_TEX_ID, texBinding); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Try to use the texture from the secondary context. - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, 1, 1); - mSecondTextureRenderer->drawTexture(); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attempt to attach to the secondary context. - mST->abandon(); - - // Attempt to attach to the primary context. - ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Attempt to attach to the primary context. - ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - AttachToContextFailsWhenAttachedBeforeUpdateTexImage) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Attempt to attach to the primary context. - ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Make there be no current display. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - // Attempt to attach with no context current. - ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Latch the texture contents on the primary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Detach from the secondary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the tertiary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mThirdEglContext)); - ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); - - // Verify that the texture object was created and bound. - GLint texBinding = -1; - glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); - EXPECT_EQ(THIRD_TEX_ID, texBinding); - - // Try to use the texture from the tertiary context. - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, 1, 1); - mThirdTextureRenderer->drawTexture(); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - AttachToContextSucceedsTwiceBeforeUpdateTexImage) { - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Detach from the primary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the secondary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Detach from the secondary context. - ASSERT_EQ(OK, mST->detachFromContext()); - - // Attach to the tertiary context. - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mThirdEglContext)); - ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID)); - - // Verify that the texture object was created and bound. - GLint texBinding = -1; - glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding); - EXPECT_EQ(THIRD_TEX_ID, texBinding); - - // Latch the texture contents on the tertiary context. - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // Try to use the texture from the tertiary context. - glClearColor(0.2, 0.2, 0.2, 0.2); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, 1, 1); - mThirdTextureRenderer->drawTexture(); - ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); -} - -TEST_F(SurfaceTextureMultiContextGLTest, - UpdateTexImageSucceedsForBufferConsumedBeforeDetach) { - ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2)); - - // produce two frames and consume them both on the primary context - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); - - // produce one more frame - ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW)); - - // Detach from the primary context and attach to the secondary context - ASSERT_EQ(OK, mST->detachFromContext()); - ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mSecondEglContext)); - ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); - - // Consume final frame on secondary context - mFW->waitForFrame(); - ASSERT_EQ(OK, mST->updateTexImage()); -} - -} // namespace android diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index e0272ba..4b6e603 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -93,7 +93,7 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) { sp<ISurfaceComposer> sf(ComposerService::getComposerService()); sp<IBinder> display(sf->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq, - 64, 64, 0, 0x7fffffff)); + 64, 64, 0, 0x7fffffff, false)); // Set the PROTECTED usage bit and verify that the screenshot fails. Note // that we need to dequeue a buffer in order for it to actually get @@ -122,7 +122,7 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) { ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1)); } ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq, - 64, 64, 0, 0x7fffffff)); + 64, 64, 0, 0x7fffffff, false)); } TEST_F(SurfaceTest, ConcreteTypeIsSurface) { diff --git a/libs/gui/tests/TextureRenderer.cpp b/libs/gui/tests/TextureRenderer.cpp new file mode 100644 index 0000000..90951b3 --- /dev/null +++ b/libs/gui/tests/TextureRenderer.cpp @@ -0,0 +1,116 @@ +/* + * 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. + */ + +#include "TextureRenderer.h" + +#include "GLTest.h" + +#include <gui/GLConsumer.h> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <gtest/gtest.h> + +namespace android { + +TextureRenderer::TextureRenderer(GLuint texName, + const sp<GLConsumer>& st) : mTexName(texName), mST(st) { +} + +void TextureRenderer::SetUp() { + const char vsrc[] = + "attribute vec4 vPosition;\n" + "varying vec2 texCoords;\n" + "uniform mat4 texMatrix;\n" + "void main() {\n" + " vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n" + " texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n" + " gl_Position = vPosition;\n" + "}\n"; + + const char fsrc[] = + "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + "uniform samplerExternalOES texSampler;\n" + "varying vec2 texCoords;\n" + "void main() {\n" + " gl_FragColor = texture2D(texSampler, texCoords);\n" + "}\n"; + + { + SCOPED_TRACE("creating shader program"); + ASSERT_NO_FATAL_FAILURE(GLTest::createProgram(vsrc, fsrc, &mPgm)); + } + + mPositionHandle = glGetAttribLocation(mPgm, "vPosition"); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_NE(-1, mPositionHandle); + mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler"); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_NE(-1, mTexSamplerHandle); + mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix"); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + ASSERT_NE(-1, mTexMatrixHandle); +} + +// drawTexture draws the GLConsumer over the entire GL viewport. +void TextureRenderer::drawTexture() { + static const GLfloat triangleVertices[] = { + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + 1.0f, 1.0f, + }; + + glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, + triangleVertices); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glEnableVertexAttribArray(mPositionHandle); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + glUseProgram(mPgm); + glUniform1i(mTexSamplerHandle, 0); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as + // they're setting the defautls for that target, but when hacking + // things to use GL_TEXTURE_2D they are needed to achieve the same + // behavior. + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, + GL_LINEAR); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); + + GLfloat texMatrix[16]; + mST->getTransformMatrix(texMatrix); + glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); +} + +} // namespace android diff --git a/libs/gui/tests/TextureRenderer.h b/libs/gui/tests/TextureRenderer.h new file mode 100644 index 0000000..37b2b47 --- /dev/null +++ b/libs/gui/tests/TextureRenderer.h @@ -0,0 +1,46 @@ +/* + * 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_TEXTURE_RENDERER_H +#define ANDROID_TEXTURE_RENDERER_H + +#include <GLES/gl.h> + +#include <utils/RefBase.h> + +namespace android { + +class GLConsumer; + +class TextureRenderer : public RefBase { +public: + TextureRenderer(GLuint texName, const sp<GLConsumer>& st); + + void SetUp(); + void drawTexture(); + +private: + GLuint mTexName; + sp<GLConsumer> mST; + GLuint mPgm; + GLint mPositionHandle; + GLint mTexSamplerHandle; + GLint mTexMatrixHandle; +}; + +} // namespace android + +#endif diff --git a/libs/input/Android.mk b/libs/input/Android.mk index f1921a4..944ac7f 100644 --- a/libs/input/Android.mk +++ b/libs/input/Android.mk @@ -27,6 +27,7 @@ commonSources := \ deviceSources := \ $(commonSources) \ + IInputFlinger.cpp \ InputTransport.cpp \ VelocityControl.cpp \ VelocityTracker.cpp diff --git a/libs/input/IInputFlinger.cpp b/libs/input/IInputFlinger.cpp new file mode 100644 index 0000000..e009731 --- /dev/null +++ b/libs/input/IInputFlinger.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <binder/Parcel.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> + +#include <input/IInputFlinger.h> + + +namespace android { + +class BpInputFlinger : public BpInterface<IInputFlinger> { +public: + BpInputFlinger(const sp<IBinder>& impl) : + BpInterface<IInputFlinger>(impl) { } + + virtual status_t doSomething() { + Parcel data, reply; + data.writeInterfaceToken(IInputFlinger::getInterfaceDescriptor()); + remote()->transact(BnInputFlinger::DO_SOMETHING_TRANSACTION, data, &reply); + return reply.readInt32(); + } +}; + +IMPLEMENT_META_INTERFACE(InputFlinger, "android.input.IInputFlinger"); + + +status_t BnInputFlinger::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + switch(code) { + case DO_SOMETHING_TRANSACTION: { + CHECK_INTERFACE(IInputFlinger, data, reply); + reply->writeInt32(0); + break; + } + default: + return BBinder::onTransact(code, data, reply, flags); + } + return NO_ERROR; +} + +}; diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index 96a7188..c4e4efa 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -235,8 +235,10 @@ status_t GraphicBuffer::flatten(void*& buffer, size_t& size, int*& fds, size_t& buffer = reinterpret_cast<void*>(static_cast<int*>(buffer) + sizeNeeded); size -= sizeNeeded; - fds += handle->numFds; - count -= handle->numFds; + if (handle) { + fds += handle->numFds; + count -= handle->numFds; + } return NO_ERROR; } diff --git a/libs/ui/PixelFormat.cpp b/libs/ui/PixelFormat.cpp index d2d103a..5ce7fba 100644 --- a/libs/ui/PixelFormat.cpp +++ b/libs/ui/PixelFormat.cpp @@ -26,6 +26,8 @@ ssize_t bytesPerPixel(PixelFormat format) { case PIXEL_FORMAT_RGBA_8888: case PIXEL_FORMAT_RGBX_8888: case PIXEL_FORMAT_BGRA_8888: + case PIXEL_FORMAT_sRGB_A_8888: + case PIXEL_FORMAT_sRGB_X_8888: return 4; case PIXEL_FORMAT_RGB_888: return 3; diff --git a/libs/ui/Region.cpp b/libs/ui/Region.cpp index 6d58f56..fa812f4 100644 --- a/libs/ui/Region.cpp +++ b/libs/ui/Region.cpp @@ -222,6 +222,22 @@ Region& Region::makeBoundsSelf() return *this; } +bool Region::contains(const Point& point) const { + return contains(point.x, point.y); +} + +bool Region::contains(int x, int y) const { + const_iterator cur = begin(); + const_iterator const tail = end(); + while (cur != tail) { + if (y >= cur->top && y < cur->bottom && x >= cur->left && x < cur->right) { + return true; + } + cur++; + } + return false; +} + void Region::clear() { mStorage.clear(); |