diff options
Diffstat (limited to 'libs')
85 files changed, 9332 insertions, 4829 deletions
diff --git a/libs/binder/Android.mk b/libs/binder/Android.mk index f3f8daf..79decfe 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 \ @@ -38,15 +39,25 @@ sources := \ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_LDLIBS += -lpthread LOCAL_MODULE := libbinder LOCAL_SHARED_LIBRARIES := liblog libcutils libutils LOCAL_SRC_FILES := $(sources) +ifneq ($(TARGET_USES_64_BIT_BINDER),true) +ifneq ($(TARGET_IS_64_BIT),true) +LOCAL_CFLAGS += -DBINDER_IPC_32BIT=1 +endif +endif +LOCAL_CFLAGS += -Werror include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) -LOCAL_LDLIBS += -lpthread LOCAL_MODULE := libbinder LOCAL_STATIC_LIBRARIES += libutils LOCAL_SRC_FILES := $(sources) +ifneq ($(TARGET_USES_64_BIT_BINDER),true) +ifneq ($(TARGET_IS_64_BIT),true) +LOCAL_CFLAGS += -DBINDER_IPC_32BIT=1 +endif +endif +LOCAL_CFLAGS += -Werror include $(BUILD_STATIC_LIBRARY) diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp index 1f21f9c..2554351 100644 --- a/libs/binder/Binder.cpp +++ b/libs/binder/Binder.cpp @@ -16,7 +16,7 @@ #include <binder/Binder.h> -#include <utils/Atomic.h> +#include <stdatomic.h> #include <utils/misc.h> #include <binder/BpBinder.h> #include <binder/IInterface.h> @@ -39,7 +39,7 @@ IBinder::~IBinder() // --------------------------------------------------------------------------- -sp<IInterface> IBinder::queryLocalInterface(const String16& descriptor) +sp<IInterface> IBinder::queryLocalInterface(const String16& /*descriptor*/) { return NULL; } @@ -71,8 +71,8 @@ public: // --------------------------------------------------------------------------- BBinder::BBinder() - : mExtras(NULL) { + atomic_init(&mExtras, 0); } bool BBinder::isBinderAlive() const @@ -117,19 +117,20 @@ status_t BBinder::transact( } status_t BBinder::linkToDeath( - const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags) + const sp<DeathRecipient>& /*recipient*/, void* /*cookie*/, + uint32_t /*flags*/) { return INVALID_OPERATION; } status_t BBinder::unlinkToDeath( - const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags, - wp<DeathRecipient>* outRecipient) + const wp<DeathRecipient>& /*recipient*/, void* /*cookie*/, + uint32_t /*flags*/, wp<DeathRecipient>* /*outRecipient*/) { return INVALID_OPERATION; } -status_t BBinder::dump(int fd, const Vector<String16>& args) + status_t BBinder::dump(int /*fd*/, const Vector<String16>& /*args*/) { return NO_ERROR; } @@ -138,14 +139,19 @@ void BBinder::attachObject( const void* objectID, void* object, void* cleanupCookie, object_cleanup_func func) { - Extras* e = mExtras; + Extras* e = reinterpret_cast<Extras*>( + atomic_load_explicit(&mExtras, memory_order_acquire)); if (!e) { e = new Extras; - if (android_atomic_cmpxchg(0, reinterpret_cast<int32_t>(e), - reinterpret_cast<volatile int32_t*>(&mExtras)) != 0) { + uintptr_t expected = 0; + if (!atomic_compare_exchange_strong_explicit( + &mExtras, &expected, + reinterpret_cast<uintptr_t>(e), + memory_order_release, + memory_order_acquire)) { delete e; - e = mExtras; + e = reinterpret_cast<Extras*>(expected); // Filled in by CAS } if (e == 0) return; // out of memory } @@ -156,7 +162,8 @@ void BBinder::attachObject( void* BBinder::findObject(const void* objectID) const { - Extras* e = mExtras; + Extras* e = reinterpret_cast<Extras*>( + atomic_load_explicit(&mExtras, memory_order_acquire)); if (!e) return NULL; AutoMutex _l(e->mLock); @@ -165,7 +172,8 @@ void* BBinder::findObject(const void* objectID) const void BBinder::detachObject(const void* objectID) { - Extras* e = mExtras; + Extras* e = reinterpret_cast<Extras*>( + atomic_load_explicit(&mExtras, memory_order_acquire)); if (!e) return; AutoMutex _l(e->mLock); @@ -179,12 +187,14 @@ BBinder* BBinder::localBinder() BBinder::~BBinder() { - if (mExtras) delete mExtras; + Extras* e = reinterpret_cast<Extras*>( + atomic_load_explicit(&mExtras, memory_order_relaxed)); + if (e) delete e; } status_t BBinder::onTransact( - uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) + uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/) { switch (code) { case INTERFACE_TRANSACTION: @@ -246,14 +256,14 @@ void BpRefBase::onFirstRef() android_atomic_or(kRemoteAcquired, &mState); } -void BpRefBase::onLastStrongRef(const void* id) +void BpRefBase::onLastStrongRef(const void* /*id*/) { if (mRemote) { mRemote->decStrong(this); } } -bool BpRefBase::onIncStrongAttempted(uint32_t flags, const void* id) +bool BpRefBase::onIncStrongAttempted(uint32_t /*flags*/, const void* /*id*/) { return mRemote ? mRefs->attemptIncStrong(this) : false; } diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp index 47a62db..101de7e 100644 --- a/libs/binder/BpBinder.cpp +++ b/libs/binder/BpBinder.cpp @@ -73,7 +73,7 @@ void BpBinder::ObjectManager::detach(const void* objectID) void BpBinder::ObjectManager::kill() { const size_t N = mObjects.size(); - ALOGV("Killing %d objects in manager %p", N, this); + ALOGV("Killing %zu objects in manager %p", N, this); for (size_t i=0; i<N; i++) { const entry_t& e = mObjects.valueAt(i); if (e.func != NULL) { @@ -119,11 +119,11 @@ const String16& BpBinder::getInterfaceDescriptor() const mDescriptorCache = res; } } - + // we're returning a reference to a non-static object here. Usually this - // is not something smart to do, however, with binder objects it is + // is not something smart to do, however, with binder objects it is // (usually) safe because they are reference-counted. - + return mDescriptorCache; } @@ -260,8 +260,8 @@ void BpBinder::sendObituary() mObitsSent = 1; mLock.unlock(); - ALOGV("Reporting death of proxy %p for %d recipients\n", - this, obits ? obits->size() : 0); + ALOGV("Reporting death of proxy %p for %zu recipients\n", + this, obits ? obits->size() : 0U); if (obits != NULL) { const size_t N = obits->size(); @@ -343,7 +343,7 @@ void BpBinder::onFirstRef() if (ipc) ipc->incStrongHandle(mHandle); } -void BpBinder::onLastStrongRef(const void* id) +void BpBinder::onLastStrongRef(const void* /*id*/) { ALOGV("onLastStrongRef BpBinder %p handle %d\n", this, mHandle); IF_ALOGV() { @@ -353,7 +353,7 @@ void BpBinder::onLastStrongRef(const void* id) if (ipc) ipc->decStrongHandle(mHandle); } -bool BpBinder::onIncStrongAttempted(uint32_t flags, const void* id) +bool BpBinder::onIncStrongAttempted(uint32_t /*flags*/, const void* /*id*/) { ALOGV("onIncStrongAttempted BpBinder %p handle %d\n", this, mHandle); IPCThreadState* ipc = IPCThreadState::self(); diff --git a/libs/binder/Debug.cpp b/libs/binder/Debug.cpp index a8b6e83..0ffafbb 100644 --- a/libs/binder/Debug.cpp +++ b/libs/binder/Debug.cpp @@ -38,7 +38,7 @@ const char* stringForIndent(int32_t indentLevel) // --------------------------------------------------------------------- -static void defaultPrintFunc(void* cookie, const char* txt) +static void defaultPrintFunc(void* /*cookie*/, const char* txt) { printf("%s", txt); } diff --git a/libs/binder/IAppOpsCallback.cpp b/libs/binder/IAppOpsCallback.cpp index e0aad23..2aaf566 100644 --- a/libs/binder/IAppOpsCallback.cpp +++ b/libs/binder/IAppOpsCallback.cpp @@ -18,7 +18,6 @@ #include <binder/IAppOpsCallback.h> -#include <utils/Debug.h> #include <utils/Log.h> #include <binder/Parcel.h> #include <utils/String8.h> diff --git a/libs/binder/IAppOpsService.cpp b/libs/binder/IAppOpsService.cpp index 471e3e9..f58a352 100644 --- a/libs/binder/IAppOpsService.cpp +++ b/libs/binder/IAppOpsService.cpp @@ -18,7 +18,6 @@ #include <binder/IAppOpsService.h> -#include <utils/Debug.h> #include <utils/Log.h> #include <binder/Parcel.h> #include <utils/String8.h> diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp new file mode 100644 index 0000000..8f3b7b4 --- /dev/null +++ b/libs/binder/IBatteryStats.cpp @@ -0,0 +1,163 @@ +/* + * 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/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); + } + + virtual void noteStartVideo(int uid) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + remote()->transact(NOTE_START_VIDEO_TRANSACTION, data, &reply); + } + + virtual void noteStopVideo(int uid) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + remote()->transact(NOTE_STOP_VIDEO_TRANSACTION, data, &reply); + } + + virtual void noteStartAudio(int uid) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply); + } + + virtual void noteStopAudio(int uid) { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + data.writeInt32(uid); + remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply); + } + + virtual void noteResetVideo() { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + remote()->transact(NOTE_RESET_VIDEO_TRANSACTION, data, &reply); + } + + virtual void noteResetAudio() { + Parcel data, reply; + data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor()); + remote()->transact(NOTE_RESET_AUDIO_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; + case NOTE_START_VIDEO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + noteStartVideo(uid); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_STOP_VIDEO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + noteStopVideo(uid); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_START_AUDIO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + noteStartAudio(uid); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_STOP_AUDIO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + int uid = data.readInt32(); + noteStopAudio(uid); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_RESET_VIDEO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + noteResetVideo(); + reply->writeNoException(); + return NO_ERROR; + } break; + case NOTE_RESET_AUDIO_TRANSACTION: { + CHECK_INTERFACE(IBatteryStats, data, reply); + noteResetAudio(); + reply->writeNoException(); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +}; // namespace android diff --git a/libs/binder/IMemory.cpp b/libs/binder/IMemory.cpp index 07cb41a..d8ed995 100644 --- a/libs/binder/IMemory.cpp +++ b/libs/binder/IMemory.cpp @@ -244,7 +244,7 @@ BpMemoryHeap::~BpMemoryHeap() { sp<IBinder> binder = const_cast<BpMemoryHeap*>(this)->asBinder(); if (VERBOSE) { - ALOGD("UNMAPPING binder=%p, heap=%p, size=%d, fd=%d", + ALOGD("UNMAPPING binder=%p, heap=%p, size=%zu, fd=%d", binder.get(), this, mSize, mHeapId); CallStack stack(LOG_TAG); } @@ -296,11 +296,11 @@ void BpMemoryHeap::assertReallyMapped() const uint32_t flags = reply.readInt32(); uint32_t offset = reply.readInt32(); - ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)", + ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%zd, err=%d (%s)", asBinder().get(), parcel_fd, size, err, strerror(-err)); int fd = dup( parcel_fd ); - ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%ld, err=%d (%s)", + ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%zd, err=%d (%s)", parcel_fd, size, err, strerror(errno)); int access = PROT_READ; @@ -313,7 +313,7 @@ void BpMemoryHeap::assertReallyMapped() const mRealHeap = true; mBase = mmap(0, size, access, MAP_SHARED, fd, offset); if (mBase == MAP_FAILED) { - ALOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)", + ALOGE("cannot map BpMemoryHeap (binder=%p), size=%zd, fd=%d (%s)", asBinder().get(), size, fd, strerror(errno)); close(fd); } else { @@ -402,7 +402,7 @@ sp<IMemoryHeap> HeapCache::find_heap(const sp<IBinder>& binder) if (i>=0) { heap_info_t& info = mHeapCache.editValueAt(i); ALOGD_IF(VERBOSE, - "found binder=%p, heap=%p, size=%d, fd=%d, count=%d", + "found binder=%p, heap=%p, size=%zu, fd=%d, count=%d", binder.get(), info.heap.get(), static_cast<BpMemoryHeap*>(info.heap.get())->mSize, static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, @@ -435,7 +435,7 @@ void HeapCache::free_heap(const wp<IBinder>& binder) int32_t c = android_atomic_dec(&info.count); if (c == 1) { ALOGD_IF(VERBOSE, - "removing binder=%p, heap=%p, size=%d, fd=%d, count=%d", + "removing binder=%p, heap=%p, size=%zu, fd=%d, count=%d", binder.unsafe_get(), info.heap.get(), static_cast<BpMemoryHeap*>(info.heap.get())->mSize, static_cast<BpMemoryHeap*>(info.heap.get())->mHeapId, @@ -466,7 +466,7 @@ void HeapCache::dump_heaps() for (int i=0 ; i<c ; i++) { const heap_info_t& info = mHeapCache.valueAt(i); BpMemoryHeap const* h(static_cast<BpMemoryHeap const *>(info.heap.get())); - ALOGD("hey=%p, heap=%p, count=%d, (fd=%d, base=%p, size=%d)", + ALOGD("hey=%p, heap=%p, count=%d, (fd=%d, base=%p, size=%zu)", mHeapCache.keyAt(i).unsafe_get(), info.heap.get(), info.count, h->mHeapId, h->mBase, h->mSize); diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index 5951a3f..dd04dcf 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -23,7 +23,6 @@ #include <binder/TextOutput.h> #include <cutils/sched_policy.h> -#include <utils/Debug.h> #include <utils/Log.h> #include <utils/threads.h> @@ -533,7 +532,7 @@ status_t IPCThreadState::handlePolledCommands() return result; } -void IPCThreadState::stopProcess(bool immediate) +void IPCThreadState::stopProcess(bool /*immediate*/) { //ALOGI("**** STOPPING PROCESS"); flushCommands(); @@ -635,6 +634,7 @@ void IPCThreadState::decWeakHandle(int32_t handle) status_t IPCThreadState::attemptIncStrongHandle(int32_t handle) { +#if HAS_BC_ATTEMPT_ACQUIRE LOG_REMOTEREFS("IPCThreadState::attemptIncStrongHandle(%d)\n", handle); mOut.writeInt32(BC_ATTEMPT_ACQUIRE); mOut.writeInt32(0); // xxx was thread priority @@ -649,6 +649,11 @@ status_t IPCThreadState::attemptIncStrongHandle(int32_t handle) #endif return result; +#else + (void)handle; + ALOGE("%s(%d): Not supported\n", __func__, handle); + return INVALID_OPERATION; +#endif } void IPCThreadState::expungeHandle(int32_t handle, IBinder* binder) @@ -663,7 +668,7 @@ status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* prox { mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION); mOut.writeInt32((int32_t)handle); - mOut.writeInt32((int32_t)proxy); + mOut.writePointer((uintptr_t)proxy); return NO_ERROR; } @@ -671,7 +676,7 @@ status_t IPCThreadState::clearDeathNotification(int32_t handle, BpBinder* proxy) { mOut.writeInt32(BC_CLEAR_DEATH_NOTIFICATION); mOut.writeInt32((int32_t)handle); - mOut.writeInt32((int32_t)proxy); + mOut.writePointer((uintptr_t)proxy); return NO_ERROR; } @@ -753,23 +758,23 @@ status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) reply->ipcSetDataReference( reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, - reinterpret_cast<const size_t*>(tr.data.ptr.offsets), - tr.offsets_size/sizeof(size_t), + reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), freeBuffer, this); } else { - err = *static_cast<const status_t*>(tr.data.ptr.buffer); + err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer); freeBuffer(NULL, reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, - reinterpret_cast<const size_t*>(tr.data.ptr.offsets), - tr.offsets_size/sizeof(size_t), this); + reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), this); } } else { freeBuffer(NULL, reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, - reinterpret_cast<const size_t*>(tr.data.ptr.offsets), - tr.offsets_size/sizeof(size_t), this); + reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), this); continue; } } @@ -809,12 +814,12 @@ status_t IPCThreadState::talkWithDriver(bool doReceive) const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; bwr.write_size = outAvail; - bwr.write_buffer = (long unsigned int)mOut.data(); + bwr.write_buffer = (uintptr_t)mOut.data(); // This is what we'll read. if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); - bwr.read_buffer = (long unsigned int)mIn.data(); + bwr.read_buffer = (uintptr_t)mIn.data(); } else { bwr.read_size = 0; bwr.read_buffer = 0; @@ -861,14 +866,14 @@ status_t IPCThreadState::talkWithDriver(bool doReceive) } while (err == -EINTR); IF_LOG_COMMANDS() { - alog << "Our err: " << (void*)err << ", write consumed: " + alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: " << bwr.write_consumed << " (of " << mOut.dataSize() << "), read consumed: " << bwr.read_consumed << endl; } if (err >= NO_ERROR) { if (bwr.write_consumed > 0) { - if (bwr.write_consumed < (ssize_t)mOut.dataSize()) + if (bwr.write_consumed < mOut.dataSize()) mOut.remove(0, bwr.write_consumed); else mOut.setDataSize(0); @@ -898,6 +903,7 @@ status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, { binder_transaction_data tr; + tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */ tr.target.handle = handle; tr.code = code; tr.flags = binderFlags; @@ -909,15 +915,15 @@ status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, if (err == NO_ERROR) { tr.data_size = data.ipcDataSize(); tr.data.ptr.buffer = data.ipcData(); - tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t); + tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t); tr.data.ptr.offsets = data.ipcObjects(); } else if (statusBuffer) { tr.flags |= TF_STATUS_CODE; *statusBuffer = err; tr.data_size = sizeof(status_t); - tr.data.ptr.buffer = statusBuffer; + tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer); tr.offsets_size = 0; - tr.data.ptr.offsets = NULL; + tr.data.ptr.offsets = 0; } else { return (mLastError = err); } @@ -950,8 +956,8 @@ status_t IPCThreadState::executeCommand(int32_t cmd) break; case BR_ACQUIRE: - refs = (RefBase::weakref_type*)mIn.readInt32(); - obj = (BBinder*)mIn.readInt32(); + refs = (RefBase::weakref_type*)mIn.readPointer(); + obj = (BBinder*)mIn.readPointer(); ALOG_ASSERT(refs->refBase() == obj, "BR_ACQUIRE: object %p does not match cookie %p (expected %p)", refs, obj, refs->refBase()); @@ -961,13 +967,13 @@ status_t IPCThreadState::executeCommand(int32_t cmd) obj->printRefs(); } mOut.writeInt32(BC_ACQUIRE_DONE); - mOut.writeInt32((int32_t)refs); - mOut.writeInt32((int32_t)obj); + mOut.writePointer((uintptr_t)refs); + mOut.writePointer((uintptr_t)obj); break; case BR_RELEASE: - refs = (RefBase::weakref_type*)mIn.readInt32(); - obj = (BBinder*)mIn.readInt32(); + refs = (RefBase::weakref_type*)mIn.readPointer(); + obj = (BBinder*)mIn.readPointer(); ALOG_ASSERT(refs->refBase() == obj, "BR_RELEASE: object %p does not match cookie %p (expected %p)", refs, obj, refs->refBase()); @@ -979,17 +985,17 @@ status_t IPCThreadState::executeCommand(int32_t cmd) break; case BR_INCREFS: - refs = (RefBase::weakref_type*)mIn.readInt32(); - obj = (BBinder*)mIn.readInt32(); + refs = (RefBase::weakref_type*)mIn.readPointer(); + obj = (BBinder*)mIn.readPointer(); refs->incWeak(mProcess.get()); mOut.writeInt32(BC_INCREFS_DONE); - mOut.writeInt32((int32_t)refs); - mOut.writeInt32((int32_t)obj); + mOut.writePointer((uintptr_t)refs); + mOut.writePointer((uintptr_t)obj); break; case BR_DECREFS: - refs = (RefBase::weakref_type*)mIn.readInt32(); - obj = (BBinder*)mIn.readInt32(); + refs = (RefBase::weakref_type*)mIn.readPointer(); + obj = (BBinder*)mIn.readPointer(); // NOTE: This assertion is not valid, because the object may no // longer exist (thus the (BBinder*)cast above resulting in a different // memory address). @@ -1000,8 +1006,8 @@ status_t IPCThreadState::executeCommand(int32_t cmd) break; case BR_ATTEMPT_ACQUIRE: - refs = (RefBase::weakref_type*)mIn.readInt32(); - obj = (BBinder*)mIn.readInt32(); + refs = (RefBase::weakref_type*)mIn.readPointer(); + obj = (BBinder*)mIn.readPointer(); { const bool success = refs->attemptIncStrong(mProcess.get()); @@ -1026,15 +1032,18 @@ status_t IPCThreadState::executeCommand(int32_t cmd) buffer.ipcSetDataReference( reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, - reinterpret_cast<const size_t*>(tr.data.ptr.offsets), - tr.offsets_size/sizeof(size_t), freeBuffer, this); + reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), freeBuffer, this); const pid_t origPid = mCallingPid; const uid_t origUid = mCallingUid; - + const int32_t origStrictModePolicy = mStrictModePolicy; + const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags; + mCallingPid = tr.sender_pid; mCallingUid = tr.sender_euid; - + mLastTransactionBinderFlags = tr.flags; + int curPrio = getpriority(PRIO_PROCESS, mMyThreadId); if (gDisableBackgroundScheduling) { if (curPrio > ANDROID_PRIORITY_NORMAL) { @@ -1056,8 +1065,9 @@ status_t IPCThreadState::executeCommand(int32_t cmd) } //ALOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid); - + Parcel reply; + status_t error; IF_LOG_TRANSACTIONS() { TextOutput::Bundle _b(alog); alog << "BR_TRANSACTION thr " << (void*)pthread_self() @@ -1071,19 +1081,18 @@ status_t IPCThreadState::executeCommand(int32_t cmd) } if (tr.target.ptr) { sp<BBinder> b((BBinder*)tr.cookie); - const status_t error = b->transact(tr.code, buffer, &reply, tr.flags); - if (error < NO_ERROR) reply.setError(error); + error = b->transact(tr.code, buffer, &reply, tr.flags); } else { - const status_t error = the_context_object->transact(tr.code, buffer, &reply, tr.flags); - if (error < NO_ERROR) reply.setError(error); + error = the_context_object->transact(tr.code, buffer, &reply, tr.flags); } - + //ALOGI("<<<< TRANSACT from pid %d restore pid %d uid %d\n", // mCallingPid, origPid, origUid); if ((tr.flags & TF_ONE_WAY) == 0) { LOG_ONEWAY("Sending reply to %d!", mCallingPid); + if (error < NO_ERROR) reply.setError(error); sendReply(reply, 0); } else { LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); @@ -1091,6 +1100,8 @@ status_t IPCThreadState::executeCommand(int32_t cmd) mCallingPid = origPid; mCallingUid = origUid; + mStrictModePolicy = origStrictModePolicy; + mLastTransactionBinderFlags = origTransactionBinderFlags; IF_LOG_TRANSACTIONS() { TextOutput::Bundle _b(alog); @@ -1103,15 +1114,15 @@ status_t IPCThreadState::executeCommand(int32_t cmd) case BR_DEAD_BINDER: { - BpBinder *proxy = (BpBinder*)mIn.readInt32(); + BpBinder *proxy = (BpBinder*)mIn.readPointer(); proxy->sendObituary(); mOut.writeInt32(BC_DEAD_BINDER_DONE); - mOut.writeInt32((int32_t)proxy); + mOut.writePointer((uintptr_t)proxy); } break; case BR_CLEAR_DEATH_NOTIFICATION_DONE: { - BpBinder *proxy = (BpBinder*)mIn.readInt32(); + BpBinder *proxy = (BpBinder*)mIn.readPointer(); proxy->getWeakRefs()->decWeak(proxy); } break; @@ -1154,9 +1165,10 @@ void IPCThreadState::threadDestructor(void *st) } -void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t dataSize, - const size_t* objects, size_t objectsSize, - void* cookie) +void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, + size_t /*dataSize*/, + const binder_size_t* /*objects*/, + size_t /*objectsSize*/, void* /*cookie*/) { //ALOGI("Freeing parcel %p", &parcel); IF_LOG_COMMANDS() { @@ -1166,7 +1178,7 @@ void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t data if (parcel != NULL) parcel->closeFileDescriptors(); IPCThreadState* state = self(); state->mOut.writeInt32(BC_FREE_BUFFER); - state->mOut.writeInt32((int32_t)data); + state->mOut.writePointer((uintptr_t)data); } }; // namespace android diff --git a/libs/binder/IPermissionController.cpp b/libs/binder/IPermissionController.cpp index e13036f..437113d 100644 --- a/libs/binder/IPermissionController.cpp +++ b/libs/binder/IPermissionController.cpp @@ -18,7 +18,6 @@ #include <binder/IPermissionController.h> -#include <utils/Debug.h> #include <utils/Log.h> #include <binder/Parcel.h> #include <utils/String8.h> diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp index a341ca8..7b1b0e7 100644 --- a/libs/binder/IServiceManager.cpp +++ b/libs/binder/IServiceManager.cpp @@ -18,7 +18,6 @@ #include <binder/IServiceManager.h> -#include <utils/Debug.h> #include <utils/Log.h> #include <binder/IPCThreadState.h> #include <binder/Parcel.h> diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp index 8d0e0a7..8739625 100644 --- a/libs/binder/MemoryDealer.cpp +++ b/libs/binder/MemoryDealer.cpp @@ -210,7 +210,7 @@ Allocation::~Allocation() #ifdef MADV_REMOVE if (size) { int err = madvise(start_ptr, size, MADV_REMOVE); - ALOGW_IF(err, "madvise(%p, %u, MADV_REMOVE) returned %s", + ALOGW_IF(err, "madvise(%p, %zu, MADV_REMOVE) returned %s", start_ptr, size, err<0 ? strerror(errno) : "Ok"); } #endif @@ -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)) { } @@ -445,8 +445,8 @@ void SimpleBestFitAllocator::dump_l(String8& result, int np = ((cur->next) && cur->next->prev != cur) ? 1 : 0; int pn = ((cur->prev) && cur->prev->next != cur) ? 2 : 0; - snprintf(buffer, SIZE, " %3u: %08x | 0x%08X | 0x%08X | %s %s\n", - i, int(cur), int(cur->start*kMemoryAlign), + snprintf(buffer, SIZE, " %3u: %p | 0x%08X | 0x%08X | %s %s\n", + i, cur, int(cur->start*kMemoryAlign), int(cur->size*kMemoryAlign), int(cur->free) ? "F" : "A", errs[np|pn]); diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index 0464e93..a24621b 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -25,6 +25,8 @@ #include <binder/ProcessState.h> #include <binder/TextOutput.h> +#include <errno.h> +#include <utils/CallStack.h> #include <utils/Debug.h> #include <utils/Log.h> #include <utils/String8.h> @@ -35,6 +37,7 @@ #include <private/binder/binder_module.h> +#include <fcntl.h> #include <inttypes.h> #include <stdio.h> #include <stdlib.h> @@ -78,12 +81,12 @@ void acquire_object(const sp<ProcessState>& proc, case BINDER_TYPE_BINDER: if (obj.binder) { LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie); - static_cast<IBinder*>(obj.cookie)->incStrong(who); + reinterpret_cast<IBinder*>(obj.cookie)->incStrong(who); } return; case BINDER_TYPE_WEAK_BINDER: if (obj.binder) - static_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who); + reinterpret_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who); return; case BINDER_TYPE_HANDLE: { const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); @@ -105,7 +108,7 @@ void acquire_object(const sp<ProcessState>& proc, } } - ALOGD("Invalid object type 0x%08lx", obj.type); + ALOGD("Invalid object type 0x%08x", obj.type); } void release_object(const sp<ProcessState>& proc, @@ -115,12 +118,12 @@ void release_object(const sp<ProcessState>& proc, case BINDER_TYPE_BINDER: if (obj.binder) { LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie); - static_cast<IBinder*>(obj.cookie)->decStrong(who); + reinterpret_cast<IBinder*>(obj.cookie)->decStrong(who); } return; case BINDER_TYPE_WEAK_BINDER: if (obj.binder) - static_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who); + reinterpret_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who); return; case BINDER_TYPE_HANDLE: { const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); @@ -136,25 +139,25 @@ void release_object(const sp<ProcessState>& proc, return; } case BINDER_TYPE_FD: { - if (obj.cookie != (void*)0) close(obj.handle); + if (obj.cookie != 0) close(obj.handle); return; } } - ALOGE("Invalid object type 0x%08lx", obj.type); + ALOGE("Invalid object type 0x%08x", obj.type); } inline static status_t finish_flatten_binder( - const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out) + const sp<IBinder>& /*binder*/, const flat_binder_object& flat, Parcel* out) { return out->writeObject(flat, false); } -status_t flatten_binder(const sp<ProcessState>& proc, +status_t flatten_binder(const sp<ProcessState>& /*proc*/, const sp<IBinder>& binder, Parcel* out) { flat_binder_object obj; - + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { IBinder *local = binder->localBinder(); @@ -165,27 +168,28 @@ status_t flatten_binder(const sp<ProcessState>& proc, } const int32_t handle = proxy ? proxy->handle() : 0; obj.type = BINDER_TYPE_HANDLE; + obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */ obj.handle = handle; - obj.cookie = NULL; + obj.cookie = 0; } else { obj.type = BINDER_TYPE_BINDER; - obj.binder = local->getWeakRefs(); - obj.cookie = local; + obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs()); + obj.cookie = reinterpret_cast<uintptr_t>(local); } } else { obj.type = BINDER_TYPE_BINDER; - obj.binder = NULL; - obj.cookie = NULL; + obj.binder = 0; + obj.cookie = 0; } - + return finish_flatten_binder(binder, obj, out); } -status_t flatten_binder(const sp<ProcessState>& proc, +status_t flatten_binder(const sp<ProcessState>& /*proc*/, const wp<IBinder>& binder, Parcel* out) { flat_binder_object obj; - + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { sp<IBinder> real = binder.promote(); @@ -198,16 +202,17 @@ status_t flatten_binder(const sp<ProcessState>& proc, } const int32_t handle = proxy ? proxy->handle() : 0; obj.type = BINDER_TYPE_WEAK_HANDLE; + obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */ obj.handle = handle; - obj.cookie = NULL; + obj.cookie = 0; } else { obj.type = BINDER_TYPE_WEAK_BINDER; - obj.binder = binder.get_refs(); - obj.cookie = binder.unsafe_get(); + obj.binder = reinterpret_cast<uintptr_t>(binder.get_refs()); + obj.cookie = reinterpret_cast<uintptr_t>(binder.unsafe_get()); } return finish_flatten_binder(real, obj, out); } - + // XXX How to deal? In order to flatten the given binder, // we need to probe it for information, which requires a primary // reference... but we don't have one. @@ -217,39 +222,40 @@ status_t flatten_binder(const sp<ProcessState>& proc, // implementation we are using. ALOGE("Unable to unflatten Binder weak reference!"); obj.type = BINDER_TYPE_BINDER; - obj.binder = NULL; - obj.cookie = NULL; + obj.binder = 0; + obj.cookie = 0; return finish_flatten_binder(NULL, obj, out); - + } else { obj.type = BINDER_TYPE_BINDER; - obj.binder = NULL; - obj.cookie = NULL; + obj.binder = 0; + obj.cookie = 0; return finish_flatten_binder(NULL, obj, out); } } inline static status_t finish_unflatten_binder( - BpBinder* proxy, const flat_binder_object& flat, const Parcel& in) + BpBinder* /*proxy*/, const flat_binder_object& /*flat*/, + const Parcel& /*in*/) { return NO_ERROR; } - + status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, sp<IBinder>* out) { const flat_binder_object* flat = in.readObject(false); - + if (flat) { switch (flat->type) { case BINDER_TYPE_BINDER: - *out = static_cast<IBinder*>(flat->cookie); + *out = reinterpret_cast<IBinder*>(flat->cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_HANDLE: *out = proc->getStrongProxyForHandle(flat->handle); return finish_unflatten_binder( static_cast<BpBinder*>(out->get()), *flat, in); - } + } } return BAD_TYPE; } @@ -258,17 +264,17 @@ status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, wp<IBinder>* out) { const flat_binder_object* flat = in.readObject(false); - + if (flat) { switch (flat->type) { case BINDER_TYPE_BINDER: - *out = static_cast<IBinder*>(flat->cookie); + *out = reinterpret_cast<IBinder*>(flat->cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_WEAK_BINDER: - if (flat->binder != NULL) { + if (flat->binder != 0) { out->set_object_and_refs( - static_cast<IBinder*>(flat->cookie), - static_cast<RefBase::weakref_type*>(flat->binder)); + reinterpret_cast<IBinder*>(flat->cookie), + reinterpret_cast<RefBase::weakref_type*>(flat->binder)); } else { *out = NULL; } @@ -332,7 +338,7 @@ status_t Parcel::setDataSize(size_t size) err = continueWrite(size); if (err == NO_ERROR) { mDataSize = size; - ALOGV("setDataSize Setting data size of %p to %d\n", this, mDataSize); + ALOGV("setDataSize Setting data size of %p to %zu", this, mDataSize); } return err; } @@ -365,7 +371,7 @@ status_t Parcel::appendFrom(const Parcel *parcel, size_t offset, size_t len) const sp<ProcessState> proc(ProcessState::self()); status_t err; const uint8_t *data = parcel->mData; - const size_t *objects = parcel->mObjects; + const binder_size_t *objects = parcel->mObjects; size_t size = parcel->mObjectsSize; int startPos = mDataPos; int firstIndex = -1, lastIndex = -2; @@ -412,15 +418,15 @@ status_t Parcel::appendFrom(const Parcel *parcel, size_t offset, size_t len) // grow objects if (mObjectsCapacity < mObjectsSize + numObjects) { int newSize = ((mObjectsSize + numObjects)*3)/2; - size_t *objects = - (size_t*)realloc(mObjects, newSize*sizeof(size_t)); - if (objects == (size_t*)0) { + binder_size_t *objects = + (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t)); + if (objects == (binder_size_t*)0) { return NO_MEMORY; } mObjects = objects; mObjectsCapacity = newSize; } - + // append and acquire objects int idx = mObjectsSize; for (int i = firstIndex; i <= lastIndex; i++) { @@ -437,7 +443,7 @@ status_t Parcel::appendFrom(const Parcel *parcel, size_t offset, size_t len) // new Parcel now owns its own fd, and can declare that we // officially know we have fds. flat->handle = dup(flat->handle); - flat->cookie = (void*)1; + flat->cookie = 1; mHasFds = mFdsKnown = true; if (!mAllowFds) { err = FDS_NOT_ALLOWED; @@ -506,13 +512,13 @@ bool Parcel::enforceInterface(const String16& interface, if (str == interface) { return true; } else { - ALOGW("**** enforceInterface() expected '%s' but read '%s'\n", + ALOGW("**** enforceInterface() expected '%s' but read '%s'", String8(interface).string(), String8(str).string()); return false; } } -const size_t* Parcel::objects() const +const binder_size_t* Parcel::objects() const { return mObjects; } @@ -536,10 +542,10 @@ status_t Parcel::finishWrite(size_t len) { //printf("Finish write of %d\n", len); mDataPos += len; - ALOGV("finishWrite Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos); if (mDataPos > mDataSize) { mDataSize = mDataPos; - ALOGV("finishWrite Setting data size of %p to %d\n", this, mDataSize); + ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize); } //printf("New pos=%d, size=%d\n", mDataPos, mDataSize); return NO_ERROR; @@ -644,6 +650,11 @@ status_t Parcel::writeInt64(int64_t val) return writeAligned(val); } +status_t Parcel::writePointer(uintptr_t val) +{ + return writeAligned<binder_uintptr_t>(val); +} + status_t Parcel::writeFloat(float val) { return writeAligned(val); @@ -670,11 +681,6 @@ status_t Parcel::writeDouble(double val) #endif -status_t Parcel::writeIntPtr(intptr_t val) -{ - return writeAligned(val); -} - status_t Parcel::writeCString(const char* str) { return write(str, strlen(str)+1); @@ -700,7 +706,7 @@ status_t Parcel::writeString16(const String16& str) status_t Parcel::writeString16(const char16_t* str, size_t len) { if (str == NULL) return writeInt32(-1); - + status_t err = writeInt32(len); if (err == NO_ERROR) { len *= sizeof(char16_t); @@ -753,14 +759,38 @@ status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) flat_binder_object obj; obj.type = BINDER_TYPE_FD; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */ obj.handle = fd; - obj.cookie = (void*) (takeOwnership ? 1 : 0); + obj.cookie = takeOwnership ? 1 : 0; return writeObject(obj, true); } status_t Parcel::writeDupFileDescriptor(int fd) { int dupFd = dup(fd); + + { // Temporary extra debug validation for b/17477219: a Parcel recipient is + // getting a positive but invalid fd unexpectedly. Trying to track down + // where it's coming from. + int dupErrno = dupFd < 0 ? errno : 0; + int fdFlags = fcntl(fd, F_GETFD); + int fdFlagsErrno = fdFlags == -1 ? errno : 0; + int dupFlags = fcntl(dupFd, F_GETFD); + int dupFlagsErrno = dupFlags == -1 ? errno : 0; + if (dupFd < 0 || fdFlags == -1 || dupFlags == -1) { + ALOGE("Parcel::writeDupFileDescriptor failed:\n" + " fd=%d flags=%d err=%d(%s)\n" + " dupFd=%d dupErr=%d(%s) flags=%d err=%d(%s)", + fd, fdFlags, fdFlagsErrno, strerror(fdFlagsErrno), + dupFd, dupErrno, strerror(dupErrno), + dupFlags, dupFlagsErrno, strerror(dupFlagsErrno)); + if (fd < 0 || fdFlags == -1) { + CallStack(LOG_TAG); + } + return -errno; + } + } + if (dupFd < 0) { return -errno; } @@ -771,6 +801,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; @@ -862,14 +918,14 @@ status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData) if (enoughData && enoughObjects) { restart_write: *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val; - + // Need to write meta-data? - if (nullMetaData || val.binder != NULL) { + if (nullMetaData || val.binder != 0) { mObjects[mObjectsSize] = mDataPos; acquire_object(ProcessState::self(), val, this); mObjectsSize++; } - + // remember if it's a file descriptor if (val.type == BINDER_TYPE_FD) { if (!mAllowFds) { @@ -887,12 +943,12 @@ restart_write: } if (!enoughObjects) { size_t newSize = ((mObjectsSize+2)*3)/2; - size_t* objects = (size_t*)realloc(mObjects, newSize*sizeof(size_t)); + binder_size_t* objects = (binder_size_t*)realloc(mObjects, newSize*sizeof(binder_size_t)); if (objects == NULL) return NO_MEMORY; mObjects = objects; mObjectsCapacity = newSize; } - + goto restart_write; } @@ -901,7 +957,7 @@ status_t Parcel::writeNoException() return writeInt32(0); } -void Parcel::remove(size_t start, size_t amt) +void Parcel::remove(size_t /*start*/, size_t /*amt*/) { LOG_ALWAYS_FATAL("Parcel::remove() not yet implemented!"); } @@ -912,7 +968,7 @@ status_t Parcel::read(void* outData, size_t len) const && len <= PAD_SIZE(len)) { memcpy(outData, mData+mDataPos, len); mDataPos += PAD_SIZE(len); - ALOGV("read Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("read Setting data pos of %p to %zu", this, mDataPos); return NO_ERROR; } return NOT_ENOUGH_DATA; @@ -924,7 +980,7 @@ const void* Parcel::readInplace(size_t len) const && len <= PAD_SIZE(len)) { const void* data = mData+mDataPos; mDataPos += PAD_SIZE(len); - ALOGV("readInplace Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("readInplace Setting data pos of %p to %zu", this, mDataPos); return data; } return NULL; @@ -991,6 +1047,22 @@ int64_t Parcel::readInt64() const return readAligned<int64_t>(); } +status_t Parcel::readPointer(uintptr_t *pArg) const +{ + status_t ret; + binder_uintptr_t ptr; + ret = readAligned(&ptr); + if (!ret) + *pArg = ptr; + return ret; +} + +uintptr_t Parcel::readPointer() const +{ + return readAligned<binder_uintptr_t>(); +} + + status_t Parcel::readFloat(float *pArg) const { return readAligned(pArg); @@ -1010,6 +1082,7 @@ status_t Parcel::readDouble(double *pArg) const double d; unsigned long long ll; } u; + u.d = 0; status_t status; status = readAligned(&u.ll); *pArg = u.d; @@ -1062,7 +1135,7 @@ const char* Parcel::readCString() const if (eos) { const size_t len = eos - str; mDataPos += PAD_SIZE(len+1); - ALOGV("readCString Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("readCString Setting data pos of %p to %zu", this, mDataPos); return str; } } @@ -1168,13 +1241,30 @@ int Parcel::readFileDescriptor() const if (flat) { switch (flat->type) { case BINDER_TYPE_FD: - //ALOGI("Returning file descriptor %ld from parcel %p\n", flat->handle, this); + //ALOGI("Returning file descriptor %ld from parcel %p", flat->handle, this); return flat->handle; - } + } } 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; @@ -1195,7 +1285,7 @@ status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const if (fd == int(BAD_TYPE)) return BAD_VALUE; void* ptr = ::mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); - if (!ptr) return NO_MEMORY; + if (ptr == MAP_FAILED) return NO_MEMORY; outBlob->init(true /*mapped*/, ptr, len); return NO_ERROR; @@ -1219,8 +1309,24 @@ status_t Parcel::read(FlattenableHelperInterface& val) const status_t err = NO_ERROR; for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) { - fds[i] = dup(this->readFileDescriptor()); - if (fds[i] < 0) err = BAD_VALUE; + int oldfd = this->readFileDescriptor(); + fds[i] = dup(oldfd); + if (fds[i] < 0) { + int dupErrno = errno; + err = BAD_VALUE; + int flags = fcntl(oldfd, F_GETFD); + int fcntlErrno = errno; + const flat_binder_object* flat = readObject(true); + ALOGE("dup failed in Parcel::read, fd %zu of %zu\n" + " dup(%d) = %d [errno: %d (%s)]\n" + " fcntl(%d, F_GETFD) = %d [errno: %d (%s)]\n" + " flat %p type %d", + i, fd_count, + oldfd, fds[i], dupErrno, strerror(dupErrno), + oldfd, flags, fcntlErrno, strerror(fcntlErrno), + flat, flat ? flat->type : 0); + CallStack(LOG_TAG); + } } if (err == NO_ERROR) { @@ -1240,23 +1346,23 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const const flat_binder_object* obj = reinterpret_cast<const flat_binder_object*>(mData+DPOS); mDataPos = DPOS + sizeof(flat_binder_object); - if (!nullMetaData && (obj->cookie == NULL && obj->binder == NULL)) { + if (!nullMetaData && (obj->cookie == 0 && obj->binder == 0)) { // When transferring a NULL object, we don't write it into // the object list, so we don't want to check for it when // reading. - ALOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos); return obj; } - + // Ensure that this object is valid... - size_t* const OBJS = mObjects; + binder_size_t* const OBJS = mObjects; const size_t N = mObjectsSize; size_t opos = mNextObjectHint; - + if (N > 0) { - ALOGV("Parcel %p looking for obj at %d, hint=%d\n", + ALOGV("Parcel %p looking for obj at %zu, hint=%zu", this, DPOS, opos); - + // Start at the current hint position, looking for an object at // the current data position. if (opos < N) { @@ -1268,27 +1374,27 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const } if (OBJS[opos] == DPOS) { // Found it! - ALOGV("Parcel found obj %d at index %d with forward search", + ALOGV("Parcel %p found obj %zu at index %zu with forward search", this, DPOS, opos); mNextObjectHint = opos+1; - ALOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos); return obj; } - + // Look backwards for it... while (opos > 0 && OBJS[opos] > DPOS) { opos--; } if (OBJS[opos] == DPOS) { // Found it! - ALOGV("Parcel found obj %d at index %d with backward search", + ALOGV("Parcel %p found obj %zu at index %zu with backward search", this, DPOS, opos); mNextObjectHint = opos+1; - ALOGV("readObject Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("readObject Setting data pos of %p to %zu", this, mDataPos); return obj; } } - ALOGW("Attempt to read object from Parcel %p at offset %d that is not in the object list", + ALOGW("Attempt to read object from Parcel %p at offset %zu that is not in the object list", this, DPOS); } return NULL; @@ -1298,22 +1404,22 @@ void Parcel::closeFileDescriptors() { size_t i = mObjectsSize; if (i > 0) { - //ALOGI("Closing file descriptors for %d objects...", mObjectsSize); + //ALOGI("Closing file descriptors for %zu objects...", i); } while (i > 0) { i--; const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]); if (flat->type == BINDER_TYPE_FD) { - //ALOGI("Closing fd: %ld\n", flat->handle); + //ALOGI("Closing fd: %ld", flat->handle); close(flat->handle); } } } -const uint8_t* Parcel::ipcData() const +uintptr_t Parcel::ipcData() const { - return mData; + return reinterpret_cast<uintptr_t>(mData); } size_t Parcel::ipcDataSize() const @@ -1321,9 +1427,9 @@ size_t Parcel::ipcDataSize() const return (mDataSize > mDataPos ? mDataSize : mDataPos); } -const size_t* Parcel::ipcObjects() const +uintptr_t Parcel::ipcObjects() const { - return mObjects; + return reinterpret_cast<uintptr_t>(mObjects); } size_t Parcel::ipcObjectsCount() const @@ -1332,26 +1438,26 @@ size_t Parcel::ipcObjectsCount() const } void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, - const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie) + const binder_size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie) { - size_t minOffset = 0; + binder_size_t minOffset = 0; freeDataNoInit(); mError = NO_ERROR; mData = const_cast<uint8_t*>(data); mDataSize = mDataCapacity = dataSize; - //ALOGI("setDataReference Setting data size of %p to %lu (pid=%d)\n", this, mDataSize, getpid()); + //ALOGI("setDataReference Setting data size of %p to %lu (pid=%d)", this, mDataSize, getpid()); mDataPos = 0; - ALOGV("setDataReference Setting data pos of %p to %d\n", this, mDataPos); - mObjects = const_cast<size_t*>(objects); + ALOGV("setDataReference Setting data pos of %p to %zu", this, mDataPos); + mObjects = const_cast<binder_size_t*>(objects); mObjectsSize = mObjectsCapacity = objectsCount; mNextObjectHint = 0; mOwner = relFunc; mOwnerCookie = relCookie; for (size_t i = 0; i < mObjectsSize; i++) { - size_t offset = mObjects[i]; + binder_size_t offset = mObjects[i]; if (offset < minOffset) { - ALOGE("%s: bad object offset %zu < %zu\n", - __func__, offset, minOffset); + ALOGE("%s: bad object offset %"PRIu64" < %"PRIu64"\n", + __func__, (uint64_t)offset, (uint64_t)minOffset); mObjectsSize = 0; break; } @@ -1360,17 +1466,17 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, scanForFds(); } -void Parcel::print(TextOutput& to, uint32_t flags) const +void Parcel::print(TextOutput& to, uint32_t /*flags*/) const { to << "Parcel("; - + if (errorCheck() != NO_ERROR) { const status_t err = errorCheck(); - to << "Error: " << (void*)err << " \"" << strerror(-err) << "\""; + to << "Error: " << (void*)(intptr_t)err << " \"" << strerror(-err) << "\""; } else if (dataSize() > 0) { const uint8_t* DATA = data(); to << indent << HexDump(DATA, dataSize()) << dedent; - const size_t* OBJS = objects(); + const binder_size_t* OBJS = objects(); const size_t N = objectsCount(); for (size_t i=0; i<N; i++) { const flat_binder_object* flat @@ -1382,7 +1488,7 @@ void Parcel::print(TextOutput& to, uint32_t flags) const } else { to << "NULL"; } - + to << ")"; } @@ -1391,7 +1497,7 @@ void Parcel::releaseObjects() const sp<ProcessState> proc(ProcessState::self()); size_t i = mObjectsSize; uint8_t* const data = mData; - size_t* const objects = mObjects; + binder_size_t* const objects = mObjects; while (i > 0) { i--; const flat_binder_object* flat @@ -1405,7 +1511,7 @@ void Parcel::acquireObjects() const sp<ProcessState> proc(ProcessState::self()); size_t i = mObjectsSize; uint8_t* const data = mData; - size_t* const objects = mObjects; + binder_size_t* const objects = mObjects; while (i > 0) { i--; const flat_binder_object* flat @@ -1423,7 +1529,7 @@ void Parcel::freeData() void Parcel::freeDataNoInit() { if (mOwner) { - //ALOGI("Freeing data ref of %p (pid=%d)\n", this, getpid()); + //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie); } else { releaseObjects(); @@ -1446,24 +1552,24 @@ status_t Parcel::restartWrite(size_t desired) freeData(); return continueWrite(desired); } - + uint8_t* data = (uint8_t*)realloc(mData, desired); if (!data && desired > mDataCapacity) { mError = NO_MEMORY; return NO_MEMORY; } - + releaseObjects(); - + if (data) { mData = data; mDataCapacity = desired; } - + mDataSize = mDataPos = 0; - ALOGV("restartWrite Setting data size of %p to %d\n", this, mDataSize); - ALOGV("restartWrite Setting data pos of %p to %d\n", this, mDataPos); - + ALOGV("restartWrite Setting data size of %p to %zu", this, mDataSize); + ALOGV("restartWrite Setting data pos of %p to %zu", this, mDataPos); + free(mObjects); mObjects = NULL; mObjectsSize = mObjectsCapacity = 0; @@ -1471,7 +1577,7 @@ status_t Parcel::restartWrite(size_t desired) mHasFds = false; mFdsKnown = true; mAllowFds = true; - + return NO_ERROR; } @@ -1491,7 +1597,7 @@ status_t Parcel::continueWrite(size_t desired) } } } - + if (mOwner) { // If the size is going to zero, just release the owner's data. if (desired == 0) { @@ -1506,10 +1612,10 @@ status_t Parcel::continueWrite(size_t desired) mError = NO_MEMORY; return NO_MEMORY; } - size_t* objects = NULL; - + binder_size_t* objects = NULL; + if (objectsSize) { - objects = (size_t*)malloc(objectsSize*sizeof(size_t)); + objects = (binder_size_t*)malloc(objectsSize*sizeof(binder_size_t)); if (!objects) { free(data); @@ -1524,21 +1630,21 @@ status_t Parcel::continueWrite(size_t desired) acquireObjects(); mObjectsSize = oldObjectsSize; } - + if (mData) { memcpy(data, mData, mDataSize < desired ? mDataSize : desired); } if (objects && mObjects) { - memcpy(objects, mObjects, objectsSize*sizeof(size_t)); + memcpy(objects, mObjects, objectsSize*sizeof(binder_size_t)); } - //ALOGI("Freeing data ref of %p (pid=%d)\n", this, getpid()); + //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie); mOwner = NULL; mData = data; mObjects = objects; mDataSize = (mDataSize < desired) ? mDataSize : desired; - ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); + ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize); mDataCapacity = desired; mObjectsSize = mObjectsCapacity = objectsSize; mNextObjectHint = 0; @@ -1556,8 +1662,8 @@ status_t Parcel::continueWrite(size_t desired) } release_object(proc, *flat, this); } - size_t* objects = - (size_t*)realloc(mObjects, objectsSize*sizeof(size_t)); + binder_size_t* objects = + (binder_size_t*)realloc(mObjects, objectsSize*sizeof(binder_size_t)); if (objects) { mObjects = objects; } @@ -1578,14 +1684,14 @@ status_t Parcel::continueWrite(size_t desired) } else { if (mDataSize > desired) { mDataSize = desired; - ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); + ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize); } if (mDataPos > desired) { mDataPos = desired; - ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos); } } - + } else { // This is the first data. Easy! uint8_t* data = (uint8_t*)malloc(desired); @@ -1596,13 +1702,13 @@ status_t Parcel::continueWrite(size_t desired) if(!(mDataCapacity == 0 && mObjects == NULL && mObjectsCapacity == 0)) { - ALOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired); + ALOGE("continueWrite: %zu/%p/%zu/%zu", mDataCapacity, mObjects, mObjectsCapacity, desired); } - + mData = data; mDataSize = mDataPos = 0; - ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); - ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize); + ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos); mDataCapacity = desired; } @@ -1616,8 +1722,8 @@ void Parcel::initState() mDataSize = 0; mDataCapacity = 0; mDataPos = 0; - ALOGV("initState Setting data size of %p to %d\n", this, mDataSize); - ALOGV("initState Setting data pos of %p to %d\n", this, mDataPos); + ALOGV("initState Setting data size of %p to %zu", this, mDataSize); + ALOGV("initState Setting data pos of %p to %zu", this, mDataPos); mObjects = NULL; mObjectsSize = 0; mObjectsCapacity = 0; diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp index c1e49bc..303d6cf 100644 --- a/libs/binder/ProcessState.cpp +++ b/libs/binder/ProcessState.cpp @@ -48,11 +48,6 @@ namespace android { -// Global variables -int mArgC; -const char* const* mArgV; -int mArgLen; - class PoolThread : public Thread { public: @@ -86,7 +81,7 @@ void ProcessState::setContextObject(const sp<IBinder>& object) setContextObject(object, String16("default")); } -sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller) +sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) { return getStrongProxyForHandle(0); } @@ -280,36 +275,6 @@ void ProcessState::expungeHandle(int32_t handle, IBinder* binder) if (e && e->binder == binder) e->binder = NULL; } -void ProcessState::setArgs(int argc, const char* const argv[]) -{ - mArgC = argc; - mArgV = (const char **)argv; - - mArgLen = 0; - for (int i=0; i<argc; i++) { - mArgLen += strlen(argv[i]) + 1; - } - mArgLen--; -} - -int ProcessState::getArgC() const -{ - return mArgC; -} - -const char* const* ProcessState::getArgV() const -{ - return mArgV; -} - -void ProcessState::setArgV0(const char* txt) -{ - if (mArgV != NULL) { - strncpy((char*)mArgV[0], txt, mArgLen); - set_process_name(txt); - } -} - String8 ProcessState::makeBinderThreadName() { int32_t s = android_atomic_add(1, &mThreadPoolSeq); String8 name; @@ -345,7 +310,7 @@ static int open_driver() int fd = open("/dev/binder", O_RDWR); if (fd >= 0) { fcntl(fd, F_SETFD, FD_CLOEXEC); - int vers; + int vers = 0; status_t result = ioctl(fd, BINDER_VERSION, &vers); if (result == -1) { ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk index c14c950..ca94aa3 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 \ @@ -16,6 +21,7 @@ LOCAL_SRC_FILES:= \ IDisplayEventConnection.cpp \ IGraphicBufferAlloc.cpp \ IGraphicBufferProducer.cpp \ + IProducerListener.cpp \ ISensorEventConnection.cpp \ ISensorServer.cpp \ ISurfaceComposer.cpp \ @@ -24,6 +30,7 @@ LOCAL_SRC_FILES:= \ Sensor.cpp \ SensorEventQueue.cpp \ SensorManager.cpp \ + StreamSplitter.cpp \ Surface.cpp \ SurfaceControl.cpp \ SurfaceComposerClient.cpp \ diff --git a/libs/gui/BitTube.cpp b/libs/gui/BitTube.cpp index 85a7de7..3ed1f37 100644 --- a/libs/gui/BitTube.cpp +++ b/libs/gui/BitTube.cpp @@ -99,6 +99,11 @@ int BitTube::getFd() const return mReceiveFd; } +int BitTube::getSendFd() const +{ + return mSendFd; +} + ssize_t BitTube::write(void const* vaddr, size_t size) { ssize_t err, len; @@ -145,7 +150,7 @@ ssize_t BitTube::sendObjects(const sp<BitTube>& tube, // should never happen because of SOCK_SEQPACKET LOG_ALWAYS_FATAL_IF((size >= 0) && (size % objSize), - "BitTube::sendObjects(count=%d, size=%d), res=%d (partial events were sent!)", + "BitTube::sendObjects(count=%zu, size=%zu), res=%zd (partial events were sent!)", count, objSize, size); //ALOGE_IF(size<0, "error %d sending %d events", size, count); @@ -160,7 +165,7 @@ ssize_t BitTube::recvObjects(const sp<BitTube>& tube, // should never happen because of SOCK_SEQPACKET LOG_ALWAYS_FATAL_IF((size >= 0) && (size % objSize), - "BitTube::recvObjects(count=%d, size=%d), res=%d (partial events were received!)", + "BitTube::recvObjects(count=%zu, size=%zu), res=%zd (partial events were received!)", count, objSize, size); //ALOGE_IF(size<0, "error %d receiving %d events", size, count); 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..c49a886 100644 --- a/libs/gui/BufferQueue.cpp +++ b/libs/gui/BufferQueue.cpp @@ -18,1226 +18,62 @@ #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/BufferQueueConsumer.h> +#include <gui/BufferQueueCore.h> +#include <gui/BufferQueueProducer.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); -} - -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"; - } -} - -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; - } -} - -BufferQueue::~BufferQueue() { - ST_LOGV("~BufferQueue"); -} - -status_t BufferQueue::setDefaultMaxBufferCountLocked(int count) { - const int minBufferCount = mUseAsyncBuffer ? 2 : 1; - if (count < minBufferCount || count > NUM_BUFFER_SLOTS) - return BAD_VALUE; - - mDefaultMaxBufferCount = count; - mDequeueCondition.broadcast(); - - return NO_ERROR; -} - -void BufferQueue::setConsumerName(const String8& name) { - Mutex::Autolock lock(mMutex); - mConsumerName = name; -} - -status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) { - Mutex::Autolock lock(mMutex); - mDefaultBufferFormat = defaultFormat; - return NO_ERROR; -} - -status_t BufferQueue::setConsumerUsageBits(uint32_t usage) { - Mutex::Autolock lock(mMutex); - mConsumerUsageBits = usage; - return NO_ERROR; -} - -status_t BufferQueue::setTransformHint(uint32_t hint) { - ST_LOGV("setTransformHint: %02x", hint); - Mutex::Autolock lock(mMutex); - mTransformHint = hint; - return NO_ERROR; -} - -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; - } +BufferQueue::ProxyConsumerListener::ProxyConsumerListener( + const wp<ConsumerListener>& consumerListener): + mConsumerListener(consumerListener) {} - // 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 +BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {} +void BufferQueue::ProxyConsumerListener::onFrameAvailable() { + sp<ConsumerListener> listener(mConsumerListener.promote()); 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; -} - -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; -} - -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); - } - - ST_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf, - mSlots[*outBuf].mFrameNumber, - mSlots[*outBuf].mGraphicBuffer->handle, returnFlags); - - return returnFlags; -} - -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; -} - -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(); -} - - -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); } -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; - } - } - +void BufferQueue::ProxyConsumerListener::onBuffersReleased() { + sp<ConsumerListener> listener(mConsumerListener.promote()); if (listener != NULL) { listener->onBuffersReleased(); } - - return err; -} - -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"); - } -} - -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; -} - -void BufferQueue::freeAllBuffersLocked() { - mBufferHasBeenQueued = false; - for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { - freeBufferLocked(i); - } -} - -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::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; } -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; -} - -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; -} - -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; -} - -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; -} - -status_t BufferQueue::setDefaultMaxBufferCount(int bufferCount) { - ATRACE_CALL(); - Mutex::Autolock lock(mMutex); - return setDefaultMaxBufferCountLocked(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; -} - -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; -} - -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; -} - -int BufferQueue::getMinMaxBufferCountLocked(bool async) const { - return getMinUndequeuedBufferCount(async) + 1; -} - -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; - } +void BufferQueue::ProxyConsumerListener::onSidebandStreamChanged() { + sp<ConsumerListener> listener(mConsumerListener.promote()); + if (listener != NULL) { + listener->onSidebandStreamChanged(); } - - return maxBufferCount; } -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); -} +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"); -BufferQueue::ProxyConsumerListener::ProxyConsumerListener( - const wp<ConsumerListener>& consumerListener): - mConsumerListener(consumerListener) {} + sp<BufferQueueCore> core(new BufferQueueCore(allocator)); + LOG_ALWAYS_FATAL_IF(core == NULL, + "BufferQueue: failed to create BufferQueueCore"); -BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {} + sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core)); + LOG_ALWAYS_FATAL_IF(producer == NULL, + "BufferQueue: failed to create BufferQueueProducer"); -void BufferQueue::ProxyConsumerListener::onFrameAvailable() { - sp<ConsumerListener> listener(mConsumerListener.promote()); - if (listener != NULL) { - listener->onFrameAvailable(); - } -} + sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core)); + LOG_ALWAYS_FATAL_IF(consumer == NULL, + "BufferQueue: failed to create BufferQueueConsumer"); -void BufferQueue::ProxyConsumerListener::onBuffersReleased() { - sp<ConsumerListener> listener(mConsumerListener.promote()); - if (listener != NULL) { - listener->onBuffersReleased(); - } + *outProducer = producer; + *outConsumer = consumer; } }; // namespace android diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp new file mode 100644 index 0000000..36e3c06 --- /dev/null +++ b/libs/gui/BufferQueueConsumer.cpp @@ -0,0 +1,523 @@ +/* + * 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 <inttypes.h> + +#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> +#include <gui/IProducerListener.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=%" PRId64 " expect=%" + PRId64 " (%" PRId64 ") now=%" PRId64, + desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + break; + } + + BQ_LOGV("acquireBuffer: drop desire=%" PRId64 " expect=%" PRId64 + " size=%zu", + 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=%" PRId64 " expect=%" PRId64 + " (%" PRId64 ") now=%" PRId64, + desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + return PRESENT_LATER; + } + + BQ_LOGV("acquireBuffer: accept desire=%" PRId64 " expect=%" PRId64 " " + "(%" PRId64 ") now=%" PRId64, desiredPresent, expectedPresent, + desiredPresent - expectedPresent, + systemTime(CLOCK_MONOTONIC)); + } + + int slot = front->mSlot; + *outBuffer = *front; + ATRACE_BUFFER_INDEX(slot); + + BQ_LOGV("acquireBuffer: acquiring { slot=%d/%" PRIu64 " 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); + + // We might have freed a slot while dropping old buffers, or the producer + // may be blocked waiting for the number of buffers in the queue to + // decrease. + 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].mNeedsCleanupOnRelease = false; + mSlots[*outSlot].mFence = Fence::NO_FENCE; + mSlots[*outSlot].mFrameNumber = 0; + + // mAcquireCalled tells BufferQueue that it doesn't need to send a valid + // GraphicBuffer pointer on the next acquireBuffer call, which decreases + // Binder traffic by not un/flattening the GraphicBuffer. However, it + // requires that the consumer maintain a cached copy of the slot <--> buffer + // mappings, which is why the consumer doesn't need the valid pointer on + // acquire. + // + // The StreamSplitter is one of the primary users of the attach/detach + // logic, and while it is running, all buffers it acquires are immediately + // detached, and all buffers it eventually releases are ones that were + // attached (as opposed to having been obtained from acquireBuffer), so it + // doesn't make sense to maintain the slot/buffer mappings, which would + // become invalid for every buffer during detach/attach. By setting this to + // false, the valid GraphicBuffer pointer will always be sent with acquire + // for attached buffers. + mSlots[*outSlot].mAcquireCalled = false; + + 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; + } + + sp<IProducerListener> listener; + { // Autolock scope + 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; + listener = mCore->mConnectedProducerListener; + BQ_LOGV("releaseBuffer: releasing slot %d", slot); + } 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(); + } // Autolock scope + + // Call back without lock held + if (listener != NULL) { + listener->onBufferReleased(); + } + + 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(uint64_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; + } + + uint64_t mask = 0; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (!mSlots[s].mAcquireCalled) { + mask |= (1ULL << 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 &= ~(1ULL << current->mSlot); + } + ++current; + } + + BQ_LOGV("getReleasedBuffers: returning mask %#" PRIx64, 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..ec1e631 --- /dev/null +++ b/libs/gui/BufferQueueCore.cpp @@ -0,0 +1,238 @@ +/* + * 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/IProducerListener.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), + mConnectedProducerListener(), + 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), + mIsAllocating(false), + mIsAllocatingCondition() +{ + 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]", + count, 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 = UINT32_MAX; + 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/%" PRIu64 " buffer=%p } " + "slot { slot=%d/%" PRIu64 " 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); +} + +void BufferQueueCore::waitWhileAllocatingLocked() const { + ATRACE_CALL(); + while (mIsAllocating) { + mIsAllocatingCondition.wait(mMutex); + } +} + +} // namespace android diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp new file mode 100644 index 0000000..d2fd3b0 --- /dev/null +++ b/libs/gui/BufferQueueProducer.cpp @@ -0,0 +1,982 @@ +/* + * 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 <inttypes.h> + +#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 <gui/IProducerListener.h> + +#include <utils/Log.h> +#include <utils/Trace.h> + +namespace android { + +BufferQueueProducer::BufferQueueProducer(const sp<BufferQueueCore>& core) : + mCore(core), + mSlots(core->mSlots), + mConsumerName(), + mStickyTransform(0) {} + +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); + mCore->waitWhileAllocatingLocked(); + + 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 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. + bool tooManyBuffers = mCore->mQueue.size() + > static_cast<size_t>(maxBufferCount); + if (tooManyBuffers) { + BQ_LOGV("%s: queue size is %zu, waiting", caller, + mCore->mQueue.size()); + } + + // If no buffer is found, or if the queue has too many buffers + // outstanding, wait for a buffer to be acquired or released, or for the + // max buffer count to change. + tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || + tooManyBuffers; + 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); + mCore->waitWhileAllocatingLocked(); + + 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; + BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot); + 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/%" PRIu64 " 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::detachNextBuffer(sp<GraphicBuffer>* outBuffer, + sp<Fence>* outFence) { + ATRACE_CALL(); + + if (outBuffer == NULL) { + BQ_LOGE("detachNextBuffer: outBuffer must not be NULL"); + return BAD_VALUE; + } else if (outFence == NULL) { + BQ_LOGE("detachNextBuffer: outFence must not be NULL"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mCore->mMutex); + mCore->waitWhileAllocatingLocked(); + + if (mCore->mIsAbandoned) { + BQ_LOGE("detachNextBuffer: BufferQueue has been abandoned"); + return NO_INIT; + } + + // Find the oldest valid slot + int found = BufferQueueCore::INVALID_BUFFER_SLOT; + for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { + if (mSlots[s].mBufferState == BufferSlot::FREE && + mSlots[s].mGraphicBuffer != NULL) { + if (found == BufferQueueCore::INVALID_BUFFER_SLOT || + mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) { + found = s; + } + } + } + + if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { + return NO_MEMORY; + } + + BQ_LOGV("detachNextBuffer detached slot %d", found); + + *outBuffer = mSlots[found].mGraphicBuffer; + *outFence = mSlots[found].mFence; + mCore->freeBufferLocked(found); + + 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); + mCore->waitWhileAllocatingLocked(); + + 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; + uint32_t stickyTransform; + bool async; + sp<Fence> fence; + input.deflate(×tamp, &isAutoTimestamp, &crop, &scalingMode, &transform, + &async, &fence, &stickyTransform); + + if (fence == NULL) { + BQ_LOGE("queueBuffer: fence is NULL"); + // Temporary workaround for b/17946343: soldier-on instead of returning an error. This + // prevents the client from dying, at the risk of visible corruption due to hwcomposer + // reading the buffer before the producer is done rendering it. Unless the buffer is the + // last frame of an animation, the corruption will be transient. + fence = Fence::NO_FENCE; + // 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/%" PRIu64 " time=%" PRIu64 + " 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; + + mStickyTransform = stickyTransform; + + 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_STICKY_TRANSFORM: + value = static_cast<int>(mStickyTransform); + 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<IProducerListener>& listener, + 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 (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; + } + + 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 (listener != NULL && + listener->asBinder()->remoteBinder() != NULL) { + status = listener->asBinder()->linkToDeath( + static_cast<IBinder::DeathRecipient*>(this)); + if (status != NO_ERROR) { + BQ_LOGE("connect(P): linkToDeath failed: %s (%d)", + strerror(-status), status); + } + } + mCore->mConnectedProducerListener = listener; + 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); + mCore->waitWhileAllocatingLocked(); + + 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 + if (mCore->mConnectedProducerListener != NULL) { + sp<IBinder> token = + mCore->mConnectedProducerListener->asBinder(); + // 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->mConnectedProducerListener = 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) { + sp<IConsumerListener> listener; + { // Autolock scope + Mutex::Autolock _l(mCore->mMutex); + mCore->mSidebandStream = stream; + listener = mCore->mConsumerListener; + } // Autolock scope + + if (listener != NULL) { + listener->onSidebandStreamChanged(); + } + return NO_ERROR; +} + +void BufferQueueProducer::allocateBuffers(bool async, uint32_t width, + uint32_t height, uint32_t format, uint32_t usage) { + ATRACE_CALL(); + while (true) { + Vector<int> freeSlots; + size_t newBufferCount = 0; + uint32_t allocWidth = 0; + uint32_t allocHeight = 0; + uint32_t allocFormat = 0; + uint32_t allocUsage = 0; + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + mCore->waitWhileAllocatingLocked(); + + int currentBufferCount = 0; + for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) { + if (mSlots[slot].mGraphicBuffer != NULL) { + ++currentBufferCount; + } else { + if (mSlots[slot].mBufferState != BufferSlot::FREE) { + BQ_LOGE("allocateBuffers: slot %d without buffer is not FREE", + slot); + continue; + } + + freeSlots.push_back(slot); + } + } + + int maxBufferCount = mCore->getMaxBufferCountLocked(async); + BQ_LOGV("allocateBuffers: allocating from %d buffers up to %d buffers", + currentBufferCount, maxBufferCount); + if (maxBufferCount <= currentBufferCount) + return; + newBufferCount = maxBufferCount - currentBufferCount; + if (freeSlots.size() < newBufferCount) { + BQ_LOGE("allocateBuffers: ran out of free slots"); + return; + } + allocWidth = width > 0 ? width : mCore->mDefaultWidth; + allocHeight = height > 0 ? height : mCore->mDefaultHeight; + allocFormat = format != 0 ? format : mCore->mDefaultBufferFormat; + allocUsage = usage | mCore->mConsumerUsageBits; + + mCore->mIsAllocating = true; + } // Autolock scope + + Vector<sp<GraphicBuffer> > buffers; + for (size_t i = 0; i < newBufferCount; ++i) { + status_t result = NO_ERROR; + sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer( + allocWidth, allocHeight, allocFormat, allocUsage, &result)); + if (result != NO_ERROR) { + BQ_LOGE("allocateBuffers: failed to allocate buffer (%u x %u, format" + " %u, usage %u)", width, height, format, usage); + Mutex::Autolock lock(mCore->mMutex); + mCore->mIsAllocating = false; + mCore->mIsAllocatingCondition.broadcast(); + return; + } + buffers.push_back(graphicBuffer); + } + + { // Autolock scope + Mutex::Autolock lock(mCore->mMutex); + uint32_t checkWidth = width > 0 ? width : mCore->mDefaultWidth; + uint32_t checkHeight = height > 0 ? height : mCore->mDefaultHeight; + uint32_t checkFormat = format != 0 ? format : mCore->mDefaultBufferFormat; + uint32_t checkUsage = usage | mCore->mConsumerUsageBits; + if (checkWidth != allocWidth || checkHeight != allocHeight || + checkFormat != allocFormat || checkUsage != allocUsage) { + // Something changed while we released the lock. Retry. + BQ_LOGV("allocateBuffers: size/format/usage changed while allocating. Retrying."); + mCore->mIsAllocating = false; + mCore->mIsAllocatingCondition.broadcast(); + continue; + } + + for (size_t i = 0; i < newBufferCount; ++i) { + int slot = freeSlots[i]; + if (mSlots[slot].mBufferState != BufferSlot::FREE) { + // A consumer allocated the FREE slot with attachBuffer. Discard the buffer we + // allocated. + BQ_LOGV("allocateBuffers: slot %d was acquired while allocating. " + "Dropping allocated buffer.", slot); + continue; + } + mCore->freeBufferLocked(slot); // Clean up the slot first + mSlots[slot].mGraphicBuffer = buffers[i]; + mSlots[slot].mFrameNumber = 0; + mSlots[slot].mFence = Fence::NO_FENCE; + BQ_LOGV("allocateBuffers: allocated a new buffer in slot %d", slot); + } + + mCore->mIsAllocating = false; + mCore->mIsAllocatingCondition.broadcast(); + } // Autolock scope + } +} + +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..f19b6c7 100644 --- a/libs/gui/ConsumerBase.cpp +++ b/libs/gui/ConsumerBase.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <inttypes.h> + #define LOG_TAG "ConsumerBase" #define ATRACE_TAG ATRACE_TAG_GRAPHICS //#define LOG_NDEBUG 0 @@ -85,7 +87,7 @@ ConsumerBase::~ConsumerBase() { "consumer is not abandoned!", mName.string()); } -void ConsumerBase::onLastStrongRef(const void* id) { +void ConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) { abandon(); } @@ -121,15 +123,18 @@ void ConsumerBase::onBuffersReleased() { return; } - uint32_t mask = 0; + uint64_t mask = 0; mConsumer->getReleasedBuffers(&mask); for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - if (mask & (1 << i)) { + if (mask & (1ULL << i)) { freeBufferLocked(i); } } } +void ConsumerBase::onSidebandStreamChanged() { +} + void ConsumerBase::abandon() { CB_LOGV("abandon"); Mutex::Autolock lock(mMutex); @@ -188,7 +193,7 @@ status_t ConsumerBase::acquireBufferLocked(BufferQueue::BufferItem *item, mSlots[item->mBuf].mFrameNumber = item->mFrameNumber; mSlots[item->mBuf].mFence = item->mFence; - CB_LOGV("acquireBufferLocked: -> slot=%d/%llu", + CB_LOGV("acquireBufferLocked: -> slot=%d/%" PRIu64, item->mBuf, item->mFrameNumber); return OK; @@ -239,11 +244,11 @@ status_t ConsumerBase::releaseBufferLocked( return OK; } - CB_LOGV("releaseBufferLocked: slot=%d/%llu", + CB_LOGV("releaseBufferLocked: slot=%d/%" PRIu64, 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/CpuConsumer.cpp b/libs/gui/CpuConsumer.cpp index bff55d1..cefd7f1 100644 --- a/libs/gui/CpuConsumer.cpp +++ b/libs/gui/CpuConsumer.cpp @@ -93,41 +93,62 @@ status_t CpuConsumer::lockNextBuffer(LockedBuffer *nativeBuffer) { int buf = b.mBuf; - if (b.mFence.get()) { - err = b.mFence->waitForever("CpuConsumer::lockNextBuffer"); - if (err != OK) { - CC_LOGE("Failed to wait for fence of acquired buffer: %s (%d)", - strerror(-err), err); - return err; - } - } - void *bufferPointer = NULL; android_ycbcr ycbcr = android_ycbcr(); - if (mSlots[buf].mGraphicBuffer->getPixelFormat() == - HAL_PIXEL_FORMAT_YCbCr_420_888) { - err = mSlots[buf].mGraphicBuffer->lockYCbCr( - GraphicBuffer::USAGE_SW_READ_OFTEN, - b.mCrop, - &ycbcr); - - if (err != OK) { - CC_LOGE("Unable to lock YCbCr buffer for CPU reading: %s (%d)", - strerror(-err), err); - return err; + if (b.mFence.get()) { + if (mSlots[buf].mGraphicBuffer->getPixelFormat() == + HAL_PIXEL_FORMAT_YCbCr_420_888) { + err = mSlots[buf].mGraphicBuffer->lockAsyncYCbCr( + GraphicBuffer::USAGE_SW_READ_OFTEN, + b.mCrop, + &ycbcr, + b.mFence->dup()); + + if (err != OK) { + CC_LOGE("Unable to lock YCbCr buffer for CPU reading: %s (%d)", + strerror(-err), err); + return err; + } + bufferPointer = ycbcr.y; + } else { + err = mSlots[buf].mGraphicBuffer->lockAsync( + GraphicBuffer::USAGE_SW_READ_OFTEN, + b.mCrop, + &bufferPointer, + b.mFence->dup()); + + if (err != OK) { + CC_LOGE("Unable to lock buffer for CPU reading: %s (%d)", + strerror(-err), err); + return err; + } } - bufferPointer = ycbcr.y; } else { - err = mSlots[buf].mGraphicBuffer->lock( - GraphicBuffer::USAGE_SW_READ_OFTEN, - b.mCrop, - &bufferPointer); - - if (err != OK) { - CC_LOGE("Unable to lock buffer for CPU reading: %s (%d)", - strerror(-err), err); - return err; + if (mSlots[buf].mGraphicBuffer->getPixelFormat() == + HAL_PIXEL_FORMAT_YCbCr_420_888) { + err = mSlots[buf].mGraphicBuffer->lockYCbCr( + GraphicBuffer::USAGE_SW_READ_OFTEN, + b.mCrop, + &ycbcr); + + if (err != OK) { + CC_LOGE("Unable to lock YCbCr buffer for CPU reading: %s (%d)", + strerror(-err), err); + return err; + } + bufferPointer = ycbcr.y; + } else { + err = mSlots[buf].mGraphicBuffer->lock( + GraphicBuffer::USAGE_SW_READ_OFTEN, + b.mCrop, + &bufferPointer); + + if (err != OK) { + CC_LOGE("Unable to lock buffer for CPU reading: %s (%d)", + strerror(-err), err); + return err; + } } } @@ -189,14 +210,22 @@ status_t CpuConsumer::unlockBuffer(const LockedBuffer &nativeBuffer) { status_t CpuConsumer::releaseAcquiredBufferLocked(int lockedIdx) { status_t err; + int fd = -1; - err = mAcquiredBuffers[lockedIdx].mGraphicBuffer->unlock(); + err = mAcquiredBuffers[lockedIdx].mGraphicBuffer->unlockAsync(&fd); if (err != OK) { CC_LOGE("%s: Unable to unlock graphic buffer %d", __FUNCTION__, lockedIdx); return err; } int buf = mAcquiredBuffers[lockedIdx].mSlot; + if (CC_LIKELY(fd != -1)) { + sp<Fence> fence(new Fence(fd)); + addReleaseFenceLocked( + mAcquiredBuffers[lockedIdx].mSlot, + mSlots[buf].mGraphicBuffer, + fence); + } // release the buffer if it hasn't already been freed by the BufferQueue. // This can happen, for example, when the producer of this buffer diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp index 3215b2f..ccafe81 100644 --- a/libs/gui/GLConsumer.cpp +++ b/libs/gui/GLConsumer.cpp @@ -143,6 +143,33 @@ GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); } +GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget, + bool useFenceSync, bool isControlledByApp) : + ConsumerBase(bq, isControlledByApp), + mCurrentTransform(0), + mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mCurrentFence(Fence::NO_FENCE), + mCurrentTimestamp(0), + mCurrentFrameNumber(0), + mDefaultWidth(1), + mDefaultHeight(1), + mFilteringEnabled(true), + mTexName(-1), + mUseFenceSync(useFenceSync), + mTexTarget(texTarget), + mEglDisplay(EGL_NO_DISPLAY), + mEglContext(EGL_NO_CONTEXT), + mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT), + mAttached(false) +{ + ST_LOGV("GLConsumer"); + + memcpy(mCurrentTransformMatrix, mtxIdentity, + sizeof(mCurrentTransformMatrix)); + + mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); +} + status_t GLConsumer::setDefaultMaxBufferCount(int bufferCount) { Mutex::Autolock lock(mMutex); return mConsumer->setDefaultMaxBufferCount(bufferCount); @@ -252,8 +279,12 @@ status_t GLConsumer::releaseTexImage() { return err; } + if (mReleasedTexImage == NULL) { + mReleasedTexImage = new EglImage(getDebugTexImageBuffer()); + } + mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; - mCurrentTextureBuf = getDebugTexImageBuffer(); + mCurrentTextureImage = mReleasedTexImage; mCurrentCrop.makeInvalid(); mCurrentTransform = 0; mCurrentScalingMode = NATIVE_WINDOW_SCALING_MODE_FREEZE; @@ -261,9 +292,11 @@ status_t GLConsumer::releaseTexImage() { mCurrentFence = Fence::NO_FENCE; if (mAttached) { - // bind a dummy texture - glBindTexture(mTexTarget, mTexName); - bindUnslottedBufferLocked(mEglDisplay); + // This binds a dummy buffer (mReleasedTexImage). + status_t err = bindTextureImageLocked(); + if (err != NO_ERROR) { + return err; + } } else { // detached, don't touch the texture (and we may not even have an // EGLDisplay here. @@ -305,29 +338,12 @@ status_t GLConsumer::acquireBufferLocked(BufferQueue::BufferItem *item, return err; } - int slot = item->mBuf; - bool destroyEglImage = false; - - if (mEglSlots[slot].mEglImage != EGL_NO_IMAGE_KHR) { - if (item->mGraphicBuffer != NULL) { - // This buffer has not been acquired before, so we must assume - // that any EGLImage in mEglSlots is stale. - destroyEglImage = true; - } else if (mEglSlots[slot].mCropRect != item->mCrop) { - // We've already seen this buffer before, but it now has a - // different crop rect, so we'll need to recreate the EGLImage if - // we're using the EGL_ANDROID_image_crop extension. - destroyEglImage = hasEglAndroidImageCrop(); - } - } - - if (destroyEglImage) { - if (!eglDestroyImageKHR(mEglDisplay, mEglSlots[slot].mEglImage)) { - ST_LOGW("acquireBufferLocked: eglDestroyImageKHR failed for slot=%d", - slot); - // keep going - } - mEglSlots[slot].mEglImage = EGL_NO_IMAGE_KHR; + // If item->mGraphicBuffer is not null, this buffer has not been acquired + // before, so any prior EglImage created is using a stale buffer. This + // replaces any old EglImage with a new one (using the new buffer). + if (item->mGraphicBuffer != NULL) { + int slot = item->mBuf; + mEglSlots[slot].mEglImage = new EglImage(item->mGraphicBuffer); } return NO_ERROR; @@ -368,29 +384,18 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferQueue::BufferItem& item) return err; } - // If the mEglSlot entry is empty, create an EGLImage for the gralloc - // buffer currently in the slot in ConsumerBase. - // + // Ensure we have a valid EglImageKHR for the slot, creating an EglImage + // if nessessary, for the gralloc buffer currently in the slot in + // ConsumerBase. // We may have to do this even when item.mGraphicBuffer == NULL (which - // means the buffer was previously acquired), if we destroyed the - // EGLImage when detaching from a context but the buffer has not been - // re-allocated. - if (mEglSlots[buf].mEglImage == EGL_NO_IMAGE_KHR) { - EGLImageKHR image = createImage(mEglDisplay, - mSlots[buf].mGraphicBuffer, item.mCrop); - if (image == EGL_NO_IMAGE_KHR) { - ST_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", - mEglDisplay, buf); - const sp<GraphicBuffer>& gb = mSlots[buf].mGraphicBuffer; - ST_LOGW("buffer size=%ux%u st=%u usage=0x%x fmt=%d", - gb->getWidth(), gb->getHeight(), gb->getStride(), - gb->getUsage(), gb->getPixelFormat()); - releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, - mEglDisplay, EGL_NO_SYNC_KHR); - return UNKNOWN_ERROR; - } - mEglSlots[buf].mEglImage = image; - mEglSlots[buf].mCropRect = item.mCrop; + // means the buffer was previously acquired). + err = mEglSlots[buf].mEglImage->createIfNeeded(mEglDisplay, item.mCrop); + if (err != NO_ERROR) { + ST_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", + mEglDisplay, buf); + releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, + mEglDisplay, EGL_NO_SYNC_KHR); + return UNKNOWN_ERROR; } // Do whatever sync ops we need to do before releasing the old slot. @@ -406,15 +411,15 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferQueue::BufferItem& item) } ST_LOGV("updateAndRelease: (slot=%d buf=%p) -> (slot=%d buf=%p)", - mCurrentTexture, - mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, + mCurrentTexture, mCurrentTextureImage != NULL ? + mCurrentTextureImage->graphicBufferHandle() : 0, buf, mSlots[buf].mGraphicBuffer->handle); // release old buffer if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { status_t status = releaseBufferLocked( - mCurrentTexture, mCurrentTextureBuf, mEglDisplay, - mEglSlots[mCurrentTexture].mEglFence); + mCurrentTexture, mCurrentTextureImage->graphicBuffer(), + mEglDisplay, mEglSlots[mCurrentTexture].mEglFence); if (status < NO_ERROR) { ST_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status), status); @@ -425,7 +430,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferQueue::BufferItem& item) // Update the GLConsumer state. mCurrentTexture = buf; - mCurrentTextureBuf = mSlots[buf].mGraphicBuffer; + mCurrentTextureImage = mEglSlots[buf].mEglImage; mCurrentCrop = item.mCrop; mCurrentTransform = item.mTransform; mCurrentScalingMode = item.mScalingMode; @@ -450,30 +455,44 @@ status_t GLConsumer::bindTextureImageLocked() { } glBindTexture(mTexTarget, mTexName); - if (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) { - if (mCurrentTextureBuf == NULL) { - ST_LOGE("bindTextureImage: no currently-bound texture"); - return NO_INIT; - } - status_t err = bindUnslottedBufferLocked(mEglDisplay); - if (err != NO_ERROR) { - return err; - } - } else { - EGLImageKHR image = mEglSlots[mCurrentTexture].mEglImage; + if (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT && + mCurrentTextureImage == NULL) { + ST_LOGE("bindTextureImage: no currently-bound texture"); + return NO_INIT; + } - glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); + status_t err = mCurrentTextureImage->createIfNeeded(mEglDisplay, + mCurrentCrop); + if (err != NO_ERROR) { + ST_LOGW("bindTextureImage: can't create image on display=%p slot=%d", + mEglDisplay, mCurrentTexture); + return UNKNOWN_ERROR; + } + mCurrentTextureImage->bindToTextureTarget(mTexTarget); - while ((error = glGetError()) != GL_NO_ERROR) { - ST_LOGE("bindTextureImage: error binding external texture image %p" - ": %#04x", image, error); + // In the rare case that the display is terminated and then initialized + // again, we can't detect that the display changed (it didn't), but the + // image is invalid. In this case, repeat the exact same steps while + // forcing the creation of a new image. + if ((error = glGetError()) != GL_NO_ERROR) { + glBindTexture(mTexTarget, mTexName); + status_t err = mCurrentTextureImage->createIfNeeded(mEglDisplay, + mCurrentCrop, + true); + if (err != NO_ERROR) { + ST_LOGW("bindTextureImage: can't create image on display=%p slot=%d", + mEglDisplay, mCurrentTexture); + return UNKNOWN_ERROR; + } + mCurrentTextureImage->bindToTextureTarget(mTexTarget); + if ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGE("bindTextureImage: error binding external image: %#04x", error); return UNKNOWN_ERROR; } } // Wait for the new buffer to be ready. return doGLFenceWaitLocked(); - } status_t GLConsumer::checkAndUpdateEglStateLocked(bool contextCheck) { @@ -510,7 +529,7 @@ void GLConsumer::setReleaseFence(const sp<Fence>& fence) { if (fence->isValid() && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { status_t err = addReleaseFence(mCurrentTexture, - mCurrentTextureBuf, fence); + mCurrentTextureImage->graphicBuffer(), fence); if (err != OK) { ST_LOGE("setReleaseFence: failed to add the fence: %s (%d)", strerror(-err), err); @@ -556,18 +575,6 @@ status_t GLConsumer::detachFromContext() { glDeleteTextures(1, &mTexName); } - // Because we're giving up the EGLDisplay we need to free all the EGLImages - // that are associated with it. They'll be recreated when the - // GLConsumer gets attached to a new OpenGL ES context (and thus gets a - // new EGLDisplay). - for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { - EGLImageKHR img = mEglSlots[i].mEglImage; - if (img != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(mEglDisplay, img); - mEglSlots[i].mEglImage = EGL_NO_IMAGE_KHR; - } - } - mEglDisplay = EGL_NO_DISPLAY; mEglContext = EGL_NO_CONTEXT; mAttached = false; @@ -608,54 +615,23 @@ status_t GLConsumer::attachToContext(uint32_t tex) { // buffer. glBindTexture(mTexTarget, GLuint(tex)); - if (mCurrentTextureBuf != NULL) { - // The EGLImageKHR that was associated with the slot was destroyed when - // the GLConsumer was detached from the old context, so we need to - // recreate it here. - status_t err = bindUnslottedBufferLocked(dpy); - if (err != NO_ERROR) { - return err; - } - } - mEglDisplay = dpy; mEglContext = ctx; mTexName = tex; mAttached = true; - return OK; -} - -status_t GLConsumer::bindUnslottedBufferLocked(EGLDisplay dpy) { - ST_LOGV("bindUnslottedBuffer ct=%d ctb=%p", - mCurrentTexture, mCurrentTextureBuf.get()); - - // Create a temporary EGLImageKHR. - Rect crop; - EGLImageKHR image = createImage(dpy, mCurrentTextureBuf, mCurrentCrop); - if (image == EGL_NO_IMAGE_KHR) { - return UNKNOWN_ERROR; - } - - // Attach the current buffer to the GL texture. - glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); - - GLint error; - status_t err = OK; - while ((error = glGetError()) != GL_NO_ERROR) { - ST_LOGE("bindUnslottedBuffer: error binding external texture image %p " - "(slot %d): %#04x", image, mCurrentTexture, error); - err = UNKNOWN_ERROR; + if (mCurrentTextureImage != NULL) { + // This may wait for a buffer a second time. This is likely required if + // this is a different context, since otherwise the wait could be skipped + // by bouncing through another context. For the same context the extra + // wait is redundant. + status_t err = bindTextureImageLocked(); + if (err != NO_ERROR) { + return err; + } } - // We destroy the EGLImageKHR here because the current buffer may no - // longer be associated with one of the buffer slots, so we have - // nowhere to to store it. If the buffer is still associated with a - // slot then another EGLImageKHR will be created next time that buffer - // gets acquired in updateTexImage. - eglDestroyImageKHR(dpy, image); - - return err; + return OK; } @@ -681,7 +657,7 @@ status_t GLConsumer::syncForReleaseLocked(EGLDisplay dpy) { } sp<Fence> fence(new Fence(fenceFd)); status_t err = addReleaseFenceLocked(mCurrentTexture, - mCurrentTextureBuf, fence); + mCurrentTextureImage->graphicBuffer(), fence); if (err != OK) { ST_LOGE("syncForReleaseLocked: error adding release fence: " "%s (%d)", strerror(-err), err); @@ -760,11 +736,11 @@ void GLConsumer::setFilteringEnabled(bool enabled) { bool needsRecompute = mFilteringEnabled != enabled; mFilteringEnabled = enabled; - if (needsRecompute && mCurrentTextureBuf==NULL) { - ST_LOGD("setFilteringEnabled called with mCurrentTextureBuf == NULL"); + if (needsRecompute && mCurrentTextureImage==NULL) { + ST_LOGD("setFilteringEnabled called with mCurrentTextureImage == NULL"); } - if (needsRecompute && mCurrentTextureBuf != NULL) { + if (needsRecompute && mCurrentTextureImage != NULL) { computeCurrentTransformMatrixLocked(); } } @@ -798,10 +774,11 @@ void GLConsumer::computeCurrentTransformMatrixLocked() { } } - sp<GraphicBuffer>& buf(mCurrentTextureBuf); + sp<GraphicBuffer> buf = (mCurrentTextureImage == NULL) ? + NULL : mCurrentTextureImage->graphicBuffer(); if (buf == NULL) { - ST_LOGD("computeCurrentTransformMatrixLocked: mCurrentTextureBuf is NULL"); + ST_LOGD("computeCurrentTransformMatrixLocked: mCurrentTextureImage is NULL"); } float mtxBeforeFlipV[16]; @@ -884,39 +861,10 @@ nsecs_t GLConsumer::getFrameNumber() { return mCurrentFrameNumber; } -EGLImageKHR GLConsumer::createImage(EGLDisplay dpy, - const sp<GraphicBuffer>& graphicBuffer, const Rect& crop) { - EGLClientBuffer cbuf = (EGLClientBuffer)graphicBuffer->getNativeBuffer(); - EGLint attrs[] = { - EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, - EGL_IMAGE_CROP_LEFT_ANDROID, crop.left, - EGL_IMAGE_CROP_TOP_ANDROID, crop.top, - EGL_IMAGE_CROP_RIGHT_ANDROID, crop.right, - EGL_IMAGE_CROP_BOTTOM_ANDROID, crop.bottom, - EGL_NONE, - }; - if (!crop.isValid()) { - // No crop rect to set, so terminate the attrib array before the crop. - attrs[2] = EGL_NONE; - } else if (!isEglImageCroppable(crop)) { - // The crop rect is not at the origin, so we can't set the crop on the - // EGLImage because that's not allowed by the EGL_ANDROID_image_crop - // extension. In the future we can add a layered extension that - // removes this restriction if there is hardware that can support it. - attrs[2] = EGL_NONE; - } - EGLImageKHR image = eglCreateImageKHR(dpy, EGL_NO_CONTEXT, - EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs); - if (image == EGL_NO_IMAGE_KHR) { - EGLint error = eglGetError(); - ST_LOGE("error creating EGLImage: %#x", error); - } - return image; -} - sp<GraphicBuffer> GLConsumer::getCurrentBuffer() const { Mutex::Autolock lock(mMutex); - return mCurrentTextureBuf; + return (mCurrentTextureImage == NULL) ? + NULL : mCurrentTextureImage->graphicBuffer(); } Rect GLConsumer::getCurrentCrop() const { @@ -1040,18 +988,13 @@ void GLConsumer::freeBufferLocked(int slotIndex) { if (slotIndex == mCurrentTexture) { mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; } - EGLImageKHR img = mEglSlots[slotIndex].mEglImage; - if (img != EGL_NO_IMAGE_KHR) { - ST_LOGV("destroying EGLImage dpy=%p img=%p", mEglDisplay, img); - eglDestroyImageKHR(mEglDisplay, img); - } - mEglSlots[slotIndex].mEglImage = EGL_NO_IMAGE_KHR; + mEglSlots[slotIndex].mEglImage.clear(); ConsumerBase::freeBufferLocked(slotIndex); } void GLConsumer::abandonLocked() { ST_LOGV("abandonLocked"); - mCurrentTextureBuf.clear(); + mCurrentTextureImage.clear(); ConsumerBase::abandonLocked(); } @@ -1111,4 +1054,88 @@ static void mtxMul(float out[16], const float a[16], const float b[16]) { out[15] = a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15]; } +GLConsumer::EglImage::EglImage(sp<GraphicBuffer> graphicBuffer) : + mGraphicBuffer(graphicBuffer), + mEglImage(EGL_NO_IMAGE_KHR), + mEglDisplay(EGL_NO_DISPLAY) { +} + +GLConsumer::EglImage::~EglImage() { + if (mEglImage != EGL_NO_IMAGE_KHR) { + if (!eglDestroyImageKHR(mEglDisplay, mEglImage)) { + ALOGE("~EglImage: eglDestroyImageKHR failed"); + } + } +} + +status_t GLConsumer::EglImage::createIfNeeded(EGLDisplay eglDisplay, + const Rect& cropRect, + bool forceCreation) { + // If there's an image and it's no longer valid, destroy it. + bool haveImage = mEglImage != EGL_NO_IMAGE_KHR; + bool displayInvalid = mEglDisplay != eglDisplay; + bool cropInvalid = hasEglAndroidImageCrop() && mCropRect != cropRect; + if (haveImage && (displayInvalid || cropInvalid || forceCreation)) { + if (!eglDestroyImageKHR(mEglDisplay, mEglImage)) { + ALOGE("createIfNeeded: eglDestroyImageKHR failed"); + } + mEglImage = EGL_NO_IMAGE_KHR; + mEglDisplay = EGL_NO_DISPLAY; + } + + // If there's no image, create one. + if (mEglImage == EGL_NO_IMAGE_KHR) { + mEglDisplay = eglDisplay; + mCropRect = cropRect; + mEglImage = createImage(mEglDisplay, mGraphicBuffer, mCropRect); + } + + // Fail if we can't create a valid image. + if (mEglImage == EGL_NO_IMAGE_KHR) { + mEglDisplay = EGL_NO_DISPLAY; + mCropRect.makeInvalid(); + const sp<GraphicBuffer>& buffer = mGraphicBuffer; + ALOGE("Failed to create image. size=%ux%u st=%u usage=0x%x fmt=%d", + buffer->getWidth(), buffer->getHeight(), buffer->getStride(), + buffer->getUsage(), buffer->getPixelFormat()); + return UNKNOWN_ERROR; + } + + return OK; +} + +void GLConsumer::EglImage::bindToTextureTarget(uint32_t texTarget) { + glEGLImageTargetTexture2DOES(texTarget, (GLeglImageOES)mEglImage); +} + +EGLImageKHR GLConsumer::EglImage::createImage(EGLDisplay dpy, + const sp<GraphicBuffer>& graphicBuffer, const Rect& crop) { + EGLClientBuffer cbuf = (EGLClientBuffer)graphicBuffer->getNativeBuffer(); + EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_IMAGE_CROP_LEFT_ANDROID, crop.left, + EGL_IMAGE_CROP_TOP_ANDROID, crop.top, + EGL_IMAGE_CROP_RIGHT_ANDROID, crop.right, + EGL_IMAGE_CROP_BOTTOM_ANDROID, crop.bottom, + EGL_NONE, + }; + if (!crop.isValid()) { + // No crop rect to set, so terminate the attrib array before the crop. + attrs[2] = EGL_NONE; + } else if (!isEglImageCroppable(crop)) { + // The crop rect is not at the origin, so we can't set the crop on the + // EGLImage because that's not allowed by the EGL_ANDROID_image_crop + // extension. In the future we can add a layered extension that + // removes this restriction if there is hardware that can support it. + attrs[2] = EGL_NONE; + } + EGLImageKHR image = eglCreateImageKHR(dpy, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs); + if (image == EGL_NO_IMAGE_KHR) { + EGLint error = eglGetError(); + ALOGE("error creating EGLImage: %#x", error); + } + return image; +} + }; // namespace android 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..dc917a8 100644 --- a/libs/gui/IGraphicBufferConsumer.cpp +++ b/libs/gui/IGraphicBufferConsumer.cpp @@ -14,16 +14,11 @@ * limitations under the License. */ -#define EGL_EGLEXT_PROTOTYPES - -#include <EGL/egl.h> -#include <EGL/eglext.h> - - #include <stdint.h> #include <sys/types.h> #include <utils/Errors.h> +#include <utils/NativeHandle.h> #include <binder/Parcel.h> #include <binder/IInterface.h> @@ -70,11 +65,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 +85,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 +132,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 +174,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 +188,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 +202,7 @@ enum { SET_DEFAULT_BUFFER_FORMAT, SET_CONSUMER_USAGE_BITS, SET_TRANSFORM_HINT, + GET_SIDEBAND_STREAM, DUMP, }; @@ -222,8 +230,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()); @@ -259,14 +292,18 @@ public: return reply.readInt32(); } - virtual status_t getReleasedBuffers(uint32_t* slotMask) { + virtual status_t getReleasedBuffers(uint64_t* slotMask) { Parcel data, reply; + if (slotMask == NULL) { + ALOGE("getReleasedBuffers: slotMask must not be NULL"); + return BAD_VALUE; + } data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor()); status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply); if (result != NO_ERROR) { return result; } - *slotMask = reply.readInt32(); + *slotMask = reply.readInt64(); return reply.readInt32(); } @@ -354,6 +391,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 +433,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 = -1; + 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(); @@ -410,9 +478,9 @@ status_t BnGraphicBufferConsumer::onTransact( } break; case GET_RELEASED_BUFFERS: { CHECK_INTERFACE(IGraphicBufferConsumer, data, reply); - uint32_t slotMask; + uint64_t slotMask; status_t result = getReleasedBuffers(&slotMask); - reply->writeInt32(slotMask); + reply->writeInt64(slotMask); reply->writeInt32(result); return NO_ERROR; } break; diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp index 3171b65..67690b7 100644 --- a/libs/gui/IGraphicBufferProducer.cpp +++ b/libs/gui/IGraphicBufferProducer.cpp @@ -18,14 +18,16 @@ #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> #include <gui/IGraphicBufferProducer.h> +#include <gui/IProducerListener.h> namespace android { // ---------------------------------------------------------------------------- @@ -34,11 +36,16 @@ enum { REQUEST_BUFFER = IBinder::FIRST_CALL_TRANSACTION, SET_BUFFER_COUNT, DEQUEUE_BUFFER, + DETACH_BUFFER, + DETACH_NEXT_BUFFER, + ATTACH_BUFFER, QUEUE_BUFFER, CANCEL_BUFFER, QUERY, CONNECT, DISCONNECT, + SET_SIDEBAND_STREAM, + ALLOCATE_BUFFERS, }; class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer> @@ -106,6 +113,62 @@ 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 detachNextBuffer(sp<GraphicBuffer>* outBuffer, + sp<Fence>* outFence) { + if (outBuffer == NULL) { + ALOGE("detachNextBuffer: outBuffer must not be NULL"); + return BAD_VALUE; + } else if (outFence == NULL) { + ALOGE("detachNextBuffer: outFence must not be NULL"); + return BAD_VALUE; + } + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + status_t result = remote()->transact(DETACH_NEXT_BUFFER, data, &reply); + if (result != NO_ERROR) { + return result; + } + result = reply.readInt32(); + if (result == NO_ERROR) { + bool nonNull = reply.readInt32(); + if (nonNull) { + *outBuffer = new GraphicBuffer; + reply.read(**outBuffer); + } + nonNull = reply.readInt32(); + if (nonNull) { + *outFence = new Fence; + reply.read(**outFence); + } + } + 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; @@ -142,11 +205,16 @@ public: return result; } - virtual status_t connect(const sp<IBinder>& token, + virtual status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp, QueueBufferOutput* output) { Parcel data, reply; data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); - data.writeStrongBinder(token); + if (listener != NULL) { + data.writeInt32(1); + data.writeStrongBinder(listener->asBinder()); + } else { + data.writeInt32(0); + } data.writeInt32(api); data.writeInt32(producerControlledByApp); status_t result = remote()->transact(CONNECT, data, &reply); @@ -169,6 +237,37 @@ 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; + } + + virtual void allocateBuffers(bool async, uint32_t width, uint32_t height, + uint32_t format, uint32_t usage) { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + data.writeInt32(static_cast<int32_t>(async)); + data.writeInt32(static_cast<int32_t>(width)); + data.writeInt32(static_cast<int32_t>(height)); + data.writeInt32(static_cast<int32_t>(format)); + data.writeInt32(static_cast<int32_t>(usage)); + status_t result = remote()->transact(ALLOCATE_BUFFERS, data, &reply); + if (result != NO_ERROR) { + ALOGE("allocateBuffers failed to transact: %d", result); + } + } }; IMPLEMENT_META_INTERFACE(GraphicBufferProducer, "android.gui.IGraphicBufferProducer"); @@ -216,6 +315,41 @@ 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 DETACH_NEXT_BUFFER: { + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + sp<GraphicBuffer> buffer; + sp<Fence> fence; + int32_t result = detachNextBuffer(&buffer, &fence); + reply->writeInt32(result); + if (result == NO_ERROR) { + reply->writeInt32(buffer != NULL); + if (buffer != NULL) { + reply->write(*buffer); + } + reply->writeInt32(fence != NULL); + if (fence != NULL) { + reply->write(*fence); + } + } + return NO_ERROR; + } break; + case ATTACH_BUFFER: { + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + sp<GraphicBuffer> buffer = new GraphicBuffer(); + data.read(*buffer.get()); + int slot = 0; + 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(); @@ -247,13 +381,16 @@ status_t BnGraphicBufferProducer::onTransact( } break; case CONNECT: { CHECK_INTERFACE(IGraphicBufferProducer, data, reply); - sp<IBinder> token = data.readStrongBinder(); + sp<IProducerListener> listener; + if (data.readInt32() == 1) { + listener = IProducerListener::asInterface(data.readStrongBinder()); + } int api = data.readInt32(); bool producerControlledByApp = data.readInt32(); QueueBufferOutput* const output = reinterpret_cast<QueueBufferOutput *>( reply->writeInplace(sizeof(QueueBufferOutput))); - status_t res = connect(token, api, producerControlledByApp, output); + status_t res = connect(listener, api, producerControlledByApp, output); reply->writeInt32(res); return NO_ERROR; } break; @@ -264,6 +401,25 @@ 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; + case ALLOCATE_BUFFERS: + CHECK_INTERFACE(IGraphicBufferProducer, data, reply); + bool async = static_cast<bool>(data.readInt32()); + uint32_t width = static_cast<uint32_t>(data.readInt32()); + uint32_t height = static_cast<uint32_t>(data.readInt32()); + uint32_t format = static_cast<uint32_t>(data.readInt32()); + uint32_t usage = static_cast<uint32_t>(data.readInt32()); + allocateBuffers(async, width, height, format, usage); + return NO_ERROR; } return BBinder::onTransact(code, data, reply, flags); } @@ -280,6 +436,7 @@ size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const { + sizeof(crop) + sizeof(scalingMode) + sizeof(transform) + + sizeof(stickyTransform) + sizeof(async) + fence->getFlattenedSize(); } @@ -299,6 +456,7 @@ status_t IGraphicBufferProducer::QueueBufferInput::flatten( FlattenableUtils::write(buffer, size, crop); FlattenableUtils::write(buffer, size, scalingMode); FlattenableUtils::write(buffer, size, transform); + FlattenableUtils::write(buffer, size, stickyTransform); FlattenableUtils::write(buffer, size, async); return fence->flatten(buffer, size, fds, count); } @@ -312,6 +470,7 @@ status_t IGraphicBufferProducer::QueueBufferInput::unflatten( + sizeof(crop) + sizeof(scalingMode) + sizeof(transform) + + sizeof(stickyTransform) + sizeof(async); if (size < minNeeded) { @@ -323,6 +482,7 @@ status_t IGraphicBufferProducer::QueueBufferInput::unflatten( FlattenableUtils::read(buffer, size, crop); FlattenableUtils::read(buffer, size, scalingMode); FlattenableUtils::read(buffer, size, transform); + FlattenableUtils::read(buffer, size, stickyTransform); FlattenableUtils::read(buffer, size, async); fence = new Fence(); diff --git a/libs/gui/IProducerListener.cpp b/libs/gui/IProducerListener.cpp new file mode 100644 index 0000000..efe4069 --- /dev/null +++ b/libs/gui/IProducerListener.cpp @@ -0,0 +1,53 @@ +/* + * 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 <binder/Parcel.h> + +#include <gui/IProducerListener.h> + +namespace android { + +enum { + ON_BUFFER_RELEASED = IBinder::FIRST_CALL_TRANSACTION, +}; + +class BpProducerListener : public BpInterface<IProducerListener> +{ +public: + BpProducerListener(const sp<IBinder>& impl) + : BpInterface<IProducerListener>(impl) {} + + virtual void onBufferReleased() { + Parcel data, reply; + data.writeInterfaceToken(IProducerListener::getInterfaceDescriptor()); + remote()->transact(ON_BUFFER_RELEASED, data, &reply, IBinder::FLAG_ONEWAY); + } +}; + +IMPLEMENT_META_INTERFACE(ProducerListener, "android.gui.IProducerListener") + +status_t BnProducerListener::onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) { + switch (code) { + case ON_BUFFER_RELEASED: + CHECK_INTERFACE(IProducerListener, data, reply); + onBufferReleased(); + return NO_ERROR; + } + return BBinder::onTransact(code, data, reply, flags); +} + +} // namespace android diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index aab0604..81e8336 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -33,6 +33,7 @@ #include <private/gui/LayerState.h> #include <ui/DisplayInfo.h> +#include <ui/DisplayStatInfo.h> #include <utils/Log.h> @@ -104,17 +105,22 @@ 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) + Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight, + uint32_t minLayerZ, uint32_t maxLayerZ, + bool useIdentityTransform, + ISurfaceComposer::Rotation rotation) { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); data.writeStrongBinder(display); data.writeStrongBinder(producer->asBinder()); + data.write(sourceCrop); data.writeInt32(reqWidth); data.writeInt32(reqHeight); data.writeInt32(minLayerZ); data.writeInt32(maxLayerZ); + data.writeInt32(static_cast<int32_t>(useIdentityTransform)); + data.writeInt32(static_cast<int32_t>(rotation)); remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply); return reply.readInt32(); } @@ -202,29 +208,83 @@ public: return reply.readStrongBinder(); } - virtual void blank(const sp<IBinder>& display) + virtual void setPowerMode(const sp<IBinder>& display, int mode) { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::BLANK, data, &reply); + data.writeInt32(mode); + remote()->transact(BnSurfaceComposer::SET_POWER_MODE, data, &reply); } - virtual void unblank(const sp<IBinder>& display) + virtual status_t getDisplayConfigs(const sp<IBinder>& display, + Vector<DisplayInfo>* configs) { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::UNBLANK, data, &reply); + remote()->transact(BnSurfaceComposer::GET_DISPLAY_CONFIGS, data, &reply); + status_t result = reply.readInt32(); + if (result == NO_ERROR) { + size_t numConfigs = static_cast<size_t>(reply.readInt32()); + configs->clear(); + configs->resize(numConfigs); + for (size_t c = 0; c < numConfigs; ++c) { + memcpy(&(configs->editItemAt(c)), + reply.readInplace(sizeof(DisplayInfo)), + sizeof(DisplayInfo)); + } + } + return result; } - virtual status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info) + virtual status_t getDisplayStats(const sp<IBinder>& display, + DisplayStatInfo* stats) { Parcel data, reply; data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); data.writeStrongBinder(display); - remote()->transact(BnSurfaceComposer::GET_DISPLAY_INFO, data, &reply); - memcpy(info, reply.readInplace(sizeof(DisplayInfo)), sizeof(DisplayInfo)); + remote()->transact(BnSurfaceComposer::GET_DISPLAY_STATS, data, &reply); + status_t result = reply.readInt32(); + if (result == NO_ERROR) { + memcpy(stats, + reply.readInplace(sizeof(DisplayStatInfo)), + sizeof(DisplayStatInfo)); + } + return result; + } + + virtual int getActiveConfig(const sp<IBinder>& display) + { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); + data.writeStrongBinder(display); + remote()->transact(BnSurfaceComposer::GET_ACTIVE_CONFIG, data, &reply); + return reply.readInt32(); + } + + virtual status_t setActiveConfig(const sp<IBinder>& display, int id) + { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); + data.writeStrongBinder(display); + data.writeInt32(id); + remote()->transact(BnSurfaceComposer::SET_ACTIVE_CONFIG, data, &reply); + return reply.readInt32(); + } + + virtual status_t clearAnimationFrameStats() { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); + remote()->transact(BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS, data, &reply); + return reply.readInt32(); + } + + virtual status_t getAnimationFrameStats(FrameStats* outStats) const { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor()); + remote()->transact(BnSurfaceComposer::GET_ANIMATION_FRAME_STATS, data, &reply); + reply.read(*outStats); return reply.readInt32(); } }; @@ -281,12 +341,19 @@ status_t BnSurfaceComposer::onTransact( sp<IBinder> display = data.readStrongBinder(); sp<IGraphicBufferProducer> producer = interface_cast<IGraphicBufferProducer>(data.readStrongBinder()); + Rect sourceCrop; + data.read(sourceCrop); uint32_t reqWidth = data.readInt32(); uint32_t reqHeight = data.readInt32(); uint32_t minLayerZ = data.readInt32(); uint32_t maxLayerZ = data.readInt32(); + bool useIdentityTransform = static_cast<bool>(data.readInt32()); + uint32_t rotation = data.readInt32(); + status_t res = captureScreen(display, producer, - reqWidth, reqHeight, minLayerZ, maxLayerZ); + sourceCrop, reqWidth, reqHeight, minLayerZ, maxLayerZ, + useIdentityTransform, + static_cast<ISurfaceComposer::Rotation>(rotation)); reply->writeInt32(res); return NO_ERROR; } @@ -325,27 +392,69 @@ status_t BnSurfaceComposer::onTransact( reply->writeStrongBinder(display); return NO_ERROR; } - case BLANK: { + case GET_DISPLAY_CONFIGS: { CHECK_INTERFACE(ISurfaceComposer, data, reply); + Vector<DisplayInfo> configs; sp<IBinder> display = data.readStrongBinder(); - blank(display); + status_t result = getDisplayConfigs(display, &configs); + reply->writeInt32(result); + if (result == NO_ERROR) { + reply->writeInt32(static_cast<int32_t>(configs.size())); + for (size_t c = 0; c < configs.size(); ++c) { + memcpy(reply->writeInplace(sizeof(DisplayInfo)), + &configs[c], sizeof(DisplayInfo)); + } + } + return NO_ERROR; + } + case GET_DISPLAY_STATS: { + CHECK_INTERFACE(ISurfaceComposer, data, reply); + DisplayStatInfo stats; + sp<IBinder> display = data.readStrongBinder(); + status_t result = getDisplayStats(display, &stats); + reply->writeInt32(result); + if (result == NO_ERROR) { + memcpy(reply->writeInplace(sizeof(DisplayStatInfo)), + &stats, sizeof(DisplayStatInfo)); + } return NO_ERROR; } - case UNBLANK: { + case GET_ACTIVE_CONFIG: { CHECK_INTERFACE(ISurfaceComposer, data, reply); sp<IBinder> display = data.readStrongBinder(); - unblank(display); + int id = getActiveConfig(display); + reply->writeInt32(id); return NO_ERROR; } - case GET_DISPLAY_INFO: { + case SET_ACTIVE_CONFIG: { CHECK_INTERFACE(ISurfaceComposer, data, reply); - DisplayInfo info; sp<IBinder> display = data.readStrongBinder(); - status_t result = getDisplayInfo(display, &info); - memcpy(reply->writeInplace(sizeof(DisplayInfo)), &info, sizeof(DisplayInfo)); + int id = data.readInt32(); + status_t result = setActiveConfig(display, id); reply->writeInt32(result); return NO_ERROR; } + case CLEAR_ANIMATION_FRAME_STATS: { + CHECK_INTERFACE(ISurfaceComposer, data, reply); + status_t result = clearAnimationFrameStats(); + reply->writeInt32(result); + return NO_ERROR; + } + case GET_ANIMATION_FRAME_STATS: { + CHECK_INTERFACE(ISurfaceComposer, data, reply); + FrameStats stats; + status_t result = getAnimationFrameStats(&stats); + reply->write(stats); + reply->writeInt32(result); + return NO_ERROR; + } + case SET_POWER_MODE: { + CHECK_INTERFACE(ISurfaceComposer, data, reply); + sp<IBinder> display = data.readStrongBinder(); + int32_t mode = data.readInt32(); + setPowerMode(display, mode); + return NO_ERROR; + } default: { return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/ISurfaceComposerClient.cpp b/libs/gui/ISurfaceComposerClient.cpp index 1adc134..3da6423 100644 --- a/libs/gui/ISurfaceComposerClient.cpp +++ b/libs/gui/ISurfaceComposerClient.cpp @@ -39,7 +39,9 @@ namespace android { enum { CREATE_SURFACE = IBinder::FIRST_CALL_TRANSACTION, - DESTROY_SURFACE + DESTROY_SURFACE, + CLEAR_LAYER_FRAME_STATS, + GET_LAYER_FRAME_STATS }; class BpSurfaceComposerClient : public BpInterface<ISurfaceComposerClient> @@ -73,6 +75,23 @@ public: remote()->transact(DESTROY_SURFACE, data, &reply); return reply.readInt32(); } + + virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposerClient::getInterfaceDescriptor()); + data.writeStrongBinder(handle); + remote()->transact(CLEAR_LAYER_FRAME_STATS, data, &reply); + return reply.readInt32(); + } + + virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const { + Parcel data, reply; + data.writeInterfaceToken(ISurfaceComposerClient::getInterfaceDescriptor()); + data.writeStrongBinder(handle); + remote()->transact(GET_LAYER_FRAME_STATS, data, &reply); + reply.read(*outStats); + return reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(SurfaceComposerClient, "android.ui.ISurfaceComposerClient"); @@ -101,7 +120,23 @@ status_t BnSurfaceComposerClient::onTransact( } break; case DESTROY_SURFACE: { CHECK_INTERFACE(ISurfaceComposerClient, data, reply); - reply->writeInt32( destroySurface( data.readStrongBinder() ) ); + reply->writeInt32(destroySurface( data.readStrongBinder() ) ); + return NO_ERROR; + } break; + case CLEAR_LAYER_FRAME_STATS: { + CHECK_INTERFACE(ISurfaceComposerClient, data, reply); + sp<IBinder> handle = data.readStrongBinder(); + status_t result = clearLayerFrameStats(handle); + reply->writeInt32(result); + return NO_ERROR; + } break; + case GET_LAYER_FRAME_STATS: { + CHECK_INTERFACE(ISurfaceComposerClient, data, reply); + sp<IBinder> handle = data.readStrongBinder(); + FrameStats stats; + status_t result = getLayerFrameStats(handle, &stats); + reply->write(stats); + reply->writeInt32(result); return NO_ERROR; } break; default: diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index acdbd77..e95d8b6 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -81,6 +81,8 @@ status_t DisplayState::write(Parcel& output) const { output.writeInt32(orientation); output.write(viewport); output.write(frame); + output.writeInt32(width); + output.writeInt32(height); return NO_ERROR; } @@ -92,6 +94,8 @@ status_t DisplayState::read(const Parcel& input) { orientation = input.readInt32(); input.read(viewport); input.read(frame); + width = input.readInt32(); + height = input.readInt32(); return NO_ERROR; } diff --git a/libs/gui/Sensor.cpp b/libs/gui/Sensor.cpp index 8f63870..b4291bb 100644 --- a/libs/gui/Sensor.cpp +++ b/libs/gui/Sensor.cpp @@ -14,8 +14,10 @@ * limitations under the License. */ +#include <inttypes.h> #include <stdint.h> #include <sys/types.h> +#include <sys/limits.h> #include <utils/Errors.h> #include <utils/String8.h> @@ -24,6 +26,7 @@ #include <hardware/sensors.h> #include <gui/Sensor.h> +#include <log/log.h> // ---------------------------------------------------------------------------- namespace android { @@ -32,7 +35,8 @@ namespace android { Sensor::Sensor() : mHandle(0), mType(0), mMinValue(0), mMaxValue(0), mResolution(0), - mPower(0), mMinDelay(0), mFifoReservedEventCount(0), mFifoMaxEventCount(0) + mPower(0), mMinDelay(0), mFifoReservedEventCount(0), mFifoMaxEventCount(0), + mMaxDelay(0), mFlags(0) { } @@ -48,10 +52,11 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion) mResolution = hwSensor->resolution; mPower = hwSensor->power; mMinDelay = hwSensor->minDelay; + mFlags = 0; // Set fifo event count zero for older devices which do not support batching. Fused // sensors also have their fifo counts set to zero. - if (halVersion >= SENSORS_DEVICE_API_VERSION_1_1) { + if (halVersion > SENSORS_DEVICE_API_VERSION_1_0) { mFifoReservedEventCount = hwSensor->fifoReservedEventCount; mFifoMaxEventCount = hwSensor->fifoMaxEventCount; } else { @@ -59,83 +64,187 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion) mFifoMaxEventCount = 0; } - // Ensure existing sensors have correct string type and required - // permissions. + if (halVersion >= SENSORS_DEVICE_API_VERSION_1_3) { + if (hwSensor->maxDelay > INT_MAX) { + // Max delay is declared as a 64 bit integer for 64 bit architectures. But it should + // always fit in a 32 bit integer, log error and cap it to INT_MAX. + ALOGE("Sensor maxDelay overflow error %s %" PRId64, mName.string(), + static_cast<int64_t>(hwSensor->maxDelay)); + mMaxDelay = INT_MAX; + } else { + mMaxDelay = (int32_t) hwSensor->maxDelay; + } + } else { + // For older hals set maxDelay to 0. + mMaxDelay = 0; + } + + // Ensure existing sensors have correct string type, required permissions and reporting mode. + // Set reportingMode for all android defined sensor types, set wake-up flag only for proximity + // sensor, significant motion, tilt, pick_up gesture, wake gesture and glance gesture on older + // HALs. Newer HALs can define both wake-up and non wake-up proximity sensors. + // All the OEM defined defined sensors have flags set to whatever is provided by the HAL. switch (mType) { case SENSOR_TYPE_ACCELEROMETER: mStringType = SENSOR_STRING_TYPE_ACCELEROMETER; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_AMBIENT_TEMPERATURE: mStringType = SENSOR_STRING_TYPE_AMBIENT_TEMPERATURE; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; break; case SENSOR_TYPE_GAME_ROTATION_VECTOR: mStringType = SENSOR_STRING_TYPE_GAME_ROTATION_VECTOR; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR: mStringType = SENSOR_STRING_TYPE_GEOMAGNETIC_ROTATION_VECTOR; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_GRAVITY: mStringType = SENSOR_STRING_TYPE_GRAVITY; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_GYROSCOPE: mStringType = SENSOR_STRING_TYPE_GYROSCOPE; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED: mStringType = SENSOR_STRING_TYPE_GYROSCOPE_UNCALIBRATED; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_HEART_RATE: mStringType = SENSOR_STRING_TYPE_HEART_RATE; mRequiredPermission = SENSOR_PERMISSION_BODY_SENSORS; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; break; case SENSOR_TYPE_LIGHT: mStringType = SENSOR_STRING_TYPE_LIGHT; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; break; case SENSOR_TYPE_LINEAR_ACCELERATION: mStringType = SENSOR_STRING_TYPE_LINEAR_ACCELERATION; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_MAGNETIC_FIELD: mStringType = SENSOR_STRING_TYPE_MAGNETIC_FIELD; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED: mStringType = SENSOR_STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_ORIENTATION: mStringType = SENSOR_STRING_TYPE_ORIENTATION; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_PRESSURE: mStringType = SENSOR_STRING_TYPE_PRESSURE; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_PROXIMITY: mStringType = SENSOR_STRING_TYPE_PROXIMITY; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } break; case SENSOR_TYPE_RELATIVE_HUMIDITY: mStringType = SENSOR_STRING_TYPE_RELATIVE_HUMIDITY; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; break; case SENSOR_TYPE_ROTATION_VECTOR: mStringType = SENSOR_STRING_TYPE_ROTATION_VECTOR; + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; break; case SENSOR_TYPE_SIGNIFICANT_MOTION: mStringType = SENSOR_STRING_TYPE_SIGNIFICANT_MOTION; + mFlags |= SENSOR_FLAG_ONE_SHOT_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } break; case SENSOR_TYPE_STEP_COUNTER: mStringType = SENSOR_STRING_TYPE_STEP_COUNTER; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; break; case SENSOR_TYPE_STEP_DETECTOR: mStringType = SENSOR_STRING_TYPE_STEP_DETECTOR; + mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE; break; case SENSOR_TYPE_TEMPERATURE: mStringType = SENSOR_STRING_TYPE_TEMPERATURE; + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; + break; + case SENSOR_TYPE_TILT_DETECTOR: + mStringType = SENSOR_STRING_TYPE_TILT_DETECTOR; + mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } + break; + case SENSOR_TYPE_WAKE_GESTURE: + mStringType = SENSOR_STRING_TYPE_WAKE_GESTURE; + mFlags |= SENSOR_FLAG_ONE_SHOT_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } + break; + case SENSOR_TYPE_GLANCE_GESTURE: + mStringType = SENSOR_STRING_TYPE_GLANCE_GESTURE; + mFlags |= SENSOR_FLAG_ONE_SHOT_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } + break; + case SENSOR_TYPE_PICK_UP_GESTURE: + mStringType = SENSOR_STRING_TYPE_PICK_UP_GESTURE; + mFlags |= SENSOR_FLAG_ONE_SHOT_MODE; + if (halVersion < SENSORS_DEVICE_API_VERSION_1_3) { + mFlags |= SENSOR_FLAG_WAKE_UP; + } break; default: - // Only pipe the stringType and requiredPermission for custom sensors. - if (halVersion >= SENSORS_DEVICE_API_VERSION_1_2 && hwSensor->stringType) { + // Only pipe the stringType, requiredPermission and flags for custom sensors. + if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor->stringType) { mStringType = hwSensor->stringType; } - if (halVersion >= SENSORS_DEVICE_API_VERSION_1_2 && hwSensor->requiredPermission) { + if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor->requiredPermission) { mRequiredPermission = hwSensor->requiredPermission; } + + if (halVersion >= SENSORS_DEVICE_API_VERSION_1_3) { + mFlags = (int32_t) hwSensor->flags; + } else { + // This is an OEM defined sensor on an older HAL. Use minDelay to determine the + // reporting mode of the sensor. + if (mMinDelay > 0) { + mFlags |= SENSOR_FLAG_CONTINUOUS_MODE; + } else if (mMinDelay == 0) { + mFlags |= SENSOR_FLAG_ON_CHANGE_MODE; + } else if (mMinDelay < 0) { + mFlags |= SENSOR_FLAG_ONE_SHOT_MODE; + } + } break; } + + // For the newer HALs log errors if reporting mask flags are set incorrectly. + if (halVersion >= SENSORS_DEVICE_API_VERSION_1_3) { + // Wake-up flag is set here. + mFlags |= (hwSensor->flags & SENSOR_FLAG_WAKE_UP); + if (mFlags != hwSensor->flags) { + int actualReportingMode = + (hwSensor->flags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT; + int expectedReportingMode = (mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT; + if (actualReportingMode != expectedReportingMode) { + ALOGE("Reporting Mode incorrect: sensor %s handle=%d type=%d " + "actual=%d expected=%d", + mName.string(), mHandle, mType, actualReportingMode, expectedReportingMode); + } + + } + } } Sensor::~Sensor() @@ -202,12 +311,28 @@ const String8& Sensor::getRequiredPermission() const { return mRequiredPermission; } +int32_t Sensor::getMaxDelay() const { + return mMaxDelay; +} + +int32_t Sensor::getFlags() const { + return mFlags; +} + +bool Sensor::isWakeUpSensor() const { + return mFlags & SENSOR_FLAG_WAKE_UP; +} + +int32_t Sensor::getReportingMode() const { + return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT); +} + size_t Sensor::getFlattenedSize() const { size_t fixedSize = sizeof(int32_t) * 3 + sizeof(float) * 4 + - sizeof(int32_t) * 3; + sizeof(int32_t) * 5; size_t variableSize = sizeof(uint32_t) + FlattenableUtils::align<4>(mName.length()) + @@ -237,6 +362,8 @@ status_t Sensor::flatten(void* buffer, size_t size) const { FlattenableUtils::write(buffer, size, mFifoMaxEventCount); flattenString8(buffer, size, mStringType); flattenString8(buffer, size, mRequiredPermission); + FlattenableUtils::write(buffer, size, mMaxDelay); + FlattenableUtils::write(buffer, size, mFlags); return NO_ERROR; } @@ -251,7 +378,7 @@ status_t Sensor::unflatten(void const* buffer, size_t size) { size_t fixedSize = sizeof(int32_t) * 3 + sizeof(float) * 4 + - sizeof(int32_t) * 3; + sizeof(int32_t) * 5; if (size < fixedSize) { return NO_MEMORY; } @@ -273,6 +400,8 @@ status_t Sensor::unflatten(void const* buffer, size_t size) { if (!unflattenString8(buffer, size, mRequiredPermission)) { return NO_MEMORY; } + FlattenableUtils::read(buffer, size, mMaxDelay); + FlattenableUtils::read(buffer, size, mFlags); return NO_ERROR; } diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp index c365671..1305e9f 100644 --- a/libs/gui/SensorEventQueue.cpp +++ b/libs/gui/SensorEventQueue.cpp @@ -18,6 +18,7 @@ #include <stdint.h> #include <sys/types.h> +#include <sys/socket.h> #include <utils/Errors.h> #include <utils/RefBase.h> @@ -35,7 +36,8 @@ namespace android { // ---------------------------------------------------------------------------- SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection) - : mSensorEventConnection(connection), mRecBuffer(NULL), mAvailable(0), mConsumed(0) { + : mSensorEventConnection(connection), mRecBuffer(NULL), mAvailable(0), mConsumed(0), + mNumAcksToSend(0) { mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT]; } @@ -144,6 +146,25 @@ status_t SensorEventQueue::setEventRate(Sensor const* sensor, nsecs_t ns) const return mSensorEventConnection->setEventRate(sensor->getHandle(), ns); } +void SensorEventQueue::sendAck(const ASensorEvent* events, int count) { + for (int i = 0; i < count; ++i) { + if (events[i].flags & WAKE_UP_SENSOR_EVENT_NEEDS_ACK) { + ++mNumAcksToSend; + } + } + // Send mNumAcksToSend to acknowledge for the wake up sensor events received. + if (mNumAcksToSend > 0) { + ssize_t size = ::send(mSensorChannel->getFd(), &mNumAcksToSend, sizeof(mNumAcksToSend), + MSG_DONTWAIT | MSG_NOSIGNAL); + if (size < 0) { + ALOGE("sendAck failure %d %d", size, mNumAcksToSend); + } else { + mNumAcksToSend = 0; + } + } + return; +} + // ---------------------------------------------------------------------------- }; // namespace android diff --git a/libs/gui/SensorManager.cpp b/libs/gui/SensorManager.cpp index b80da56..7b4fa2f 100644 --- a/libs/gui/SensorManager.cpp +++ b/libs/gui/SensorManager.cpp @@ -116,12 +116,23 @@ Sensor const* SensorManager::getDefaultSensor(int type) { Mutex::Autolock _l(mLock); if (assertStateLocked() == NO_ERROR) { + bool wakeUpSensor = false; + // For the following sensor types, return a wake-up sensor. These types are by default + // defined as wake-up sensors. For the rest of the sensor types defined in sensors.h return + // a non_wake-up version. + if (type == SENSOR_TYPE_PROXIMITY || type == SENSOR_TYPE_SIGNIFICANT_MOTION || + type == SENSOR_TYPE_TILT_DETECTOR || type == SENSOR_TYPE_WAKE_GESTURE || + type == SENSOR_TYPE_GLANCE_GESTURE || type == SENSOR_TYPE_PICK_UP_GESTURE) { + wakeUpSensor = true; + } // For now we just return the first sensor of that type we find. // in the future it will make sense to let the SensorService make // that decision. for (size_t i=0 ; i<mSensors.size() ; i++) { - if (mSensorList[i]->getType() == type) + if (mSensorList[i]->getType() == type && + mSensorList[i]->isWakeUpSensor() == wakeUpSensor) { return mSensorList[i]; + } } } return NULL; diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp new file mode 100644 index 0000000..771b263 --- /dev/null +++ b/libs/gui/StreamSplitter.cpp @@ -0,0 +1,284 @@ +/* + * 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 <inttypes.h> + +#define LOG_TAG "StreamSplitter" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#include <gui/IGraphicBufferConsumer.h> +#include <gui/IGraphicBufferProducer.h> +#include <gui/StreamSplitter.h> + +#include <ui/GraphicBuffer.h> + +#include <binder/ProcessState.h> + +#include <utils/Trace.h> + +namespace android { + +status_t StreamSplitter::createSplitter( + const sp<IGraphicBufferConsumer>& inputQueue, + sp<StreamSplitter>* outSplitter) { + if (inputQueue == NULL) { + ALOGE("createSplitter: inputQueue must not be NULL"); + return BAD_VALUE; + } + if (outSplitter == NULL) { + ALOGE("createSplitter: outSplitter must not be NULL"); + return BAD_VALUE; + } + + sp<StreamSplitter> splitter(new StreamSplitter(inputQueue)); + status_t status = splitter->mInput->consumerConnect(splitter, false); + if (status == NO_ERROR) { + splitter->mInput->setConsumerName(String8("StreamSplitter")); + *outSplitter = splitter; + } + return status; +} + +StreamSplitter::StreamSplitter(const sp<IGraphicBufferConsumer>& inputQueue) + : mIsAbandoned(false), mMutex(), mReleaseCondition(), + mOutstandingBuffers(0), mInput(inputQueue), mOutputs(), mBuffers() {} + +StreamSplitter::~StreamSplitter() { + mInput->consumerDisconnect(); + Vector<sp<IGraphicBufferProducer> >::iterator output = mOutputs.begin(); + for (; output != mOutputs.end(); ++output) { + (*output)->disconnect(NATIVE_WINDOW_API_CPU); + } + + if (mBuffers.size() > 0) { + ALOGE("%zu buffers still being tracked", mBuffers.size()); + } +} + +status_t StreamSplitter::addOutput( + const sp<IGraphicBufferProducer>& outputQueue) { + if (outputQueue == NULL) { + ALOGE("addOutput: outputQueue must not be NULL"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + + IGraphicBufferProducer::QueueBufferOutput queueBufferOutput; + sp<OutputListener> listener(new OutputListener(this, outputQueue)); + outputQueue->asBinder()->linkToDeath(listener); + status_t status = outputQueue->connect(listener, NATIVE_WINDOW_API_CPU, + /* producerControlledByApp */ false, &queueBufferOutput); + if (status != NO_ERROR) { + ALOGE("addOutput: failed to connect (%d)", status); + return status; + } + + mOutputs.push_back(outputQueue); + + return NO_ERROR; +} + +void StreamSplitter::setName(const String8 &name) { + Mutex::Autolock lock(mMutex); + mInput->setConsumerName(name); +} + +void StreamSplitter::onFrameAvailable() { + ATRACE_CALL(); + Mutex::Autolock lock(mMutex); + + // The current policy is that if any one consumer is consuming buffers too + // slowly, the splitter will stall the rest of the outputs by not acquiring + // any more buffers from the input. This will cause back pressure on the + // input queue, slowing down its producer. + + // If there are too many outstanding buffers, we block until a buffer is + // released back to the input in onBufferReleased + while (mOutstandingBuffers >= MAX_OUTSTANDING_BUFFERS) { + mReleaseCondition.wait(mMutex); + + // If the splitter is abandoned while we are waiting, the release + // condition variable will be broadcast, and we should just return + // without attempting to do anything more (since the input queue will + // also be abandoned). + if (mIsAbandoned) { + return; + } + } + ++mOutstandingBuffers; + + // Acquire and detach the buffer from the input + IGraphicBufferConsumer::BufferItem bufferItem; + status_t status = mInput->acquireBuffer(&bufferItem, /* presentWhen */ 0); + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "acquiring buffer from input failed (%d)", status); + + ALOGV("acquired buffer %#" PRIx64 " from input", + bufferItem.mGraphicBuffer->getId()); + + status = mInput->detachBuffer(bufferItem.mBuf); + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "detaching buffer from input failed (%d)", status); + + // Initialize our reference count for this buffer + mBuffers.add(bufferItem.mGraphicBuffer->getId(), + new BufferTracker(bufferItem.mGraphicBuffer)); + + IGraphicBufferProducer::QueueBufferInput queueInput( + bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp, + bufferItem.mCrop, bufferItem.mScalingMode, + bufferItem.mTransform, bufferItem.mIsDroppable, + bufferItem.mFence); + + // Attach and queue the buffer to each of the outputs + Vector<sp<IGraphicBufferProducer> >::iterator output = mOutputs.begin(); + for (; output != mOutputs.end(); ++output) { + int slot; + status = (*output)->attachBuffer(&slot, bufferItem.mGraphicBuffer); + if (status == NO_INIT) { + // If we just discovered that this output has been abandoned, note + // that, increment the release count so that we still release this + // buffer eventually, and move on to the next output + onAbandonedLocked(); + mBuffers.editValueFor(bufferItem.mGraphicBuffer->getId())-> + incrementReleaseCountLocked(); + continue; + } else { + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "attaching buffer to output failed (%d)", status); + } + + IGraphicBufferProducer::QueueBufferOutput queueOutput; + status = (*output)->queueBuffer(slot, queueInput, &queueOutput); + if (status == NO_INIT) { + // If we just discovered that this output has been abandoned, note + // that, increment the release count so that we still release this + // buffer eventually, and move on to the next output + onAbandonedLocked(); + mBuffers.editValueFor(bufferItem.mGraphicBuffer->getId())-> + incrementReleaseCountLocked(); + continue; + } else { + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "queueing buffer to output failed (%d)", status); + } + + ALOGV("queued buffer %#" PRIx64 " to output %p", + bufferItem.mGraphicBuffer->getId(), output->get()); + } +} + +void StreamSplitter::onBufferReleasedByOutput( + const sp<IGraphicBufferProducer>& from) { + ATRACE_CALL(); + Mutex::Autolock lock(mMutex); + + sp<GraphicBuffer> buffer; + sp<Fence> fence; + status_t status = from->detachNextBuffer(&buffer, &fence); + if (status == NO_INIT) { + // If we just discovered that this output has been abandoned, note that, + // but we can't do anything else, since buffer is invalid + onAbandonedLocked(); + return; + } else { + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "detaching buffer from output failed (%d)", status); + } + + ALOGV("detached buffer %#" PRIx64 " from output %p", + buffer->getId(), from.get()); + + const sp<BufferTracker>& tracker = mBuffers.editValueFor(buffer->getId()); + + // Merge the release fence of the incoming buffer so that the fence we send + // back to the input includes all of the outputs' fences + tracker->mergeFence(fence); + + // Check to see if this is the last outstanding reference to this buffer + size_t releaseCount = tracker->incrementReleaseCountLocked(); + ALOGV("buffer %#" PRIx64 " reference count %zu (of %zu)", buffer->getId(), + releaseCount, mOutputs.size()); + if (releaseCount < mOutputs.size()) { + return; + } + + // If we've been abandoned, we can't return the buffer to the input, so just + // stop tracking it and move on + if (mIsAbandoned) { + mBuffers.removeItem(buffer->getId()); + return; + } + + // Attach and release the buffer back to the input + int consumerSlot; + status = mInput->attachBuffer(&consumerSlot, tracker->getBuffer()); + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "attaching buffer to input failed (%d)", status); + + status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence()); + LOG_ALWAYS_FATAL_IF(status != NO_ERROR, + "releasing buffer to input failed (%d)", status); + + ALOGV("released buffer %#" PRIx64 " to input", buffer->getId()); + + // We no longer need to track the buffer once it has been returned to the + // input + mBuffers.removeItem(buffer->getId()); + + // Notify any waiting onFrameAvailable calls + --mOutstandingBuffers; + mReleaseCondition.signal(); +} + +void StreamSplitter::onAbandonedLocked() { + ALOGE("one of my outputs has abandoned me"); + if (!mIsAbandoned) { + mInput->consumerDisconnect(); + } + mIsAbandoned = true; + mReleaseCondition.broadcast(); +} + +StreamSplitter::OutputListener::OutputListener( + const sp<StreamSplitter>& splitter, + const sp<IGraphicBufferProducer>& output) + : mSplitter(splitter), mOutput(output) {} + +StreamSplitter::OutputListener::~OutputListener() {} + +void StreamSplitter::OutputListener::onBufferReleased() { + mSplitter->onBufferReleasedByOutput(mOutput); +} + +void StreamSplitter::OutputListener::binderDied(const wp<IBinder>& /* who */) { + Mutex::Autolock lock(mSplitter->mMutex); + mSplitter->onAbandonedLocked(); +} + +StreamSplitter::BufferTracker::BufferTracker(const sp<GraphicBuffer>& buffer) + : mBuffer(buffer), mMergedFence(Fence::NO_FENCE), mReleaseCount(0) {} + +StreamSplitter::BufferTracker::~BufferTracker() {} + +void StreamSplitter::BufferTracker::mergeFence(const sp<Fence>& with) { + mMergedFence = Fence::merge(String8("StreamSplitter"), mMergedFence, with); +} + +} // namespace android diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 27dbc4e..0e2baa2 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -24,9 +24,11 @@ #include <utils/Log.h> #include <utils/Trace.h> +#include <utils/NativeHandle.h> #include <ui/Fence.h> +#include <gui/IProducerListener.h> #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> #include <gui/GLConsumer.h> @@ -65,6 +67,7 @@ Surface::Surface( mCrop.clear(); mScalingMode = NATIVE_WINDOW_SCALING_MODE_FREEZE; mTransform = 0; + mStickyTransform = 0; mDefaultWidth = 0; mDefaultHeight = 0; mUserWidth = 0; @@ -86,6 +89,17 @@ sp<IGraphicBufferProducer> Surface::getIGraphicBufferProducer() const { return mGraphicBufferProducer; } +void Surface::setSidebandStream(const sp<NativeHandle>& stream) { + mGraphicBufferProducer->setSidebandStream(stream); +} + +void Surface::allocateBuffers() { + uint32_t reqWidth = mReqWidth ? mReqWidth : mUserWidth; + uint32_t reqHeight = mReqHeight ? mReqHeight : mUserHeight; + mGraphicBufferProducer->allocateBuffers(mSwapIntervalZero, mReqWidth, + mReqHeight, mReqFormat, mReqUsage); +} + int Surface::hook_setSwapInterval(ANativeWindow* window, int interval) { Surface* c = getSelf(window); return c->setSwapInterval(interval); @@ -178,19 +192,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 @@ -204,6 +237,7 @@ int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) { result = mGraphicBufferProducer->requestBuffer(buf, &gbuf); if (result != NO_ERROR) { ALOGE("dequeueBuffer: IGraphicBufferProducer::requestBuffer failed: %d", result); + mGraphicBufferProducer->cancelBuffer(buf, fence); return result; } } @@ -251,7 +285,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; @@ -284,15 +318,22 @@ int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) { sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE); IGraphicBufferProducer::QueueBufferOutput output; IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp, - crop, mScalingMode, mTransform, mSwapIntervalZero, fence); + crop, mScalingMode, mTransform ^ mStickyTransform, mSwapIntervalZero, + fence, mStickyTransform); status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output); if (err != OK) { ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err); } uint32_t numPendingBuffers = 0; - output.deflate(&mDefaultWidth, &mDefaultHeight, &mTransformHint, + uint32_t hint = 0; + output.deflate(&mDefaultWidth, &mDefaultHeight, &hint, &numPendingBuffers); + // Disable transform hint if sticky transform is set. + if (mStickyTransform == 0) { + mTransformHint = hint; + } + mConsumerRunningBehind = (numPendingBuffers >= 2); return err; @@ -374,6 +415,9 @@ int Surface::perform(int operation, va_list args) case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: res = dispatchSetBuffersTransform(args); break; + case NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM: + res = dispatchSetBuffersStickyTransform(args); + break; case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP: res = dispatchSetBuffersTimestamp(args); break; @@ -401,6 +445,9 @@ int Surface::perform(int operation, va_list args) case NATIVE_WINDOW_API_DISCONNECT: res = dispatchDisconnect(args); break; + case NATIVE_WINDOW_SET_SIDEBAND_STREAM: + res = dispatchSetSidebandStream(args); + break; default: res = NAME_NOT_FOUND; break; @@ -471,6 +518,11 @@ int Surface::dispatchSetBuffersTransform(va_list args) { return setBuffersTransform(transform); } +int Surface::dispatchSetBuffersStickyTransform(va_list args) { + int transform = va_arg(args, int); + return setBuffersStickyTransform(transform); +} + int Surface::dispatchSetBuffersTimestamp(va_list args) { int64_t timestamp = va_arg(args, int64_t); return setBuffersTimestamp(timestamp); @@ -482,22 +534,35 @@ 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(); } +int Surface::dispatchSetSidebandStream(va_list args) { + native_handle_t* sH = va_arg(args, native_handle_t*); + sp<NativeHandle> sidebandHandle = NativeHandle::create(sH, false); + setSidebandStream(sidebandHandle); + return OK; +} int Surface::connect(int api) { ATRACE_CALL(); ALOGV("Surface::connect"); - static sp<BBinder> sLife = new BBinder(); + static sp<IProducerListener> listener = new DummyProducerListener(); Mutex::Autolock lock(mMutex); IGraphicBufferProducer::QueueBufferOutput output; - int err = mGraphicBufferProducer->connect(sLife, api, mProducerControlledByApp, &output); + int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output); if (err == NO_ERROR) { uint32_t numPendingBuffers = 0; - output.deflate(&mDefaultWidth, &mDefaultHeight, &mTransformHint, + uint32_t hint = 0; + output.deflate(&mDefaultWidth, &mDefaultHeight, &hint, &numPendingBuffers); + + // Disable transform hint if sticky transform is set. + if (mStickyTransform == 0) { + mTransformHint = hint; + } + mConsumerRunningBehind = (numPendingBuffers >= 2); } if (!err && api == NATIVE_WINDOW_API_CPU) { @@ -521,6 +586,8 @@ int Surface::disconnect(int api) { mCrop.clear(); mScalingMode = NATIVE_WINDOW_SCALING_MODE_FREEZE; mTransform = 0; + mStickyTransform = 0; + if (api == NATIVE_WINDOW_API_CPU) { mConnectedToCpu = false; } @@ -647,6 +714,15 @@ int Surface::setBuffersTransform(int transform) return NO_ERROR; } +int Surface::setBuffersStickyTransform(int transform) +{ + ATRACE_CALL(); + ALOGV("Surface::setBuffersStickyTransform"); + Mutex::Autolock lock(mMutex); + mStickyTransform = transform; + return NO_ERROR; +} + int Surface::setBuffersTimestamp(int64_t timestamp) { ALOGV("Surface::setBuffersTimestamp"); @@ -740,15 +816,6 @@ status_t Surface::lock( ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err)); if (err == NO_ERROR) { sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out)); - sp<Fence> fence(new Fence(fenceFd)); - - err = fence->waitForever("Surface::lock"); - if (err != OK) { - ALOGE("Fence::wait failed (%s)", strerror(-err)); - cancelBuffer(out, fenceFd); - return err; - } - const Rect bounds(backBuffer->width, backBuffer->height); Region newDirtyRegion; @@ -799,9 +866,9 @@ status_t Surface::lock( } void* vaddr; - status_t res = backBuffer->lock( + status_t res = backBuffer->lockAsync( GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, - newDirtyRegion.bounds(), &vaddr); + newDirtyRegion.bounds(), &vaddr, fenceFd); ALOGW_IF(res, "failed locking buffer (handle = %p)", backBuffer->handle); @@ -827,10 +894,11 @@ status_t Surface::unlockAndPost() return INVALID_OPERATION; } - status_t err = mLockedBuffer->unlock(); + int fd = -1; + status_t err = mLockedBuffer->unlockAsync(&fd); ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle); - err = queueBuffer(mLockedBuffer.get(), -1); + err = queueBuffer(mLockedBuffer.get(), fd); ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)", mLockedBuffer->handle, strerror(-err)); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 2246f5f..6446926 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -166,6 +166,7 @@ public: uint32_t orientation, const Rect& layerStackRect, const Rect& displayRect); + void setDisplaySize(const sp<IBinder>& token, uint32_t width, uint32_t height); static void setAnimationTransaction() { Composer::getInstance().setAnimationTransactionImpl(); @@ -426,6 +427,14 @@ void Composer::setDisplayProjection(const sp<IBinder>& token, mForceSynchronous = true; // TODO: do we actually still need this? } +void Composer::setDisplaySize(const sp<IBinder>& token, uint32_t width, uint32_t height) { + Mutex::Autolock _l(mLock); + DisplayState& s(getDisplayStateLocked(token)); + s.width = width; + s.height = height; + s.what |= DisplayState::eDisplaySizeChanged; +} + // --------------------------------------------------------------------------- SurfaceComposerClient::SurfaceComposerClient() @@ -515,6 +524,21 @@ status_t SurfaceComposerClient::destroySurface(const sp<IBinder>& sid) { return err; } +status_t SurfaceComposerClient::clearLayerFrameStats(const sp<IBinder>& token) const { + if (mStatus != NO_ERROR) { + return mStatus; + } + return mClient->clearLayerFrameStats(token); +} + +status_t SurfaceComposerClient::getLayerFrameStats(const sp<IBinder>& token, + FrameStats* outStats) const { + if (mStatus != NO_ERROR) { + return mStatus; + } + return mClient->getLayerFrameStats(token, outStats); +} + inline Composer& SurfaceComposerClient::getComposer() { return mComposer; } @@ -606,20 +630,56 @@ void SurfaceComposerClient::setDisplayProjection(const sp<IBinder>& token, layerStackRect, displayRect); } +void SurfaceComposerClient::setDisplaySize(const sp<IBinder>& token, + uint32_t width, uint32_t height) { + Composer::getInstance().setDisplaySize(token, width, height); +} + // ---------------------------------------------------------------------------- -status_t SurfaceComposerClient::getDisplayInfo( - const sp<IBinder>& display, DisplayInfo* info) +status_t SurfaceComposerClient::getDisplayConfigs( + const sp<IBinder>& display, Vector<DisplayInfo>* configs) { - return ComposerService::getComposerService()->getDisplayInfo(display, info); + return ComposerService::getComposerService()->getDisplayConfigs(display, configs); +} + +status_t SurfaceComposerClient::getDisplayInfo(const sp<IBinder>& display, + DisplayInfo* info) { + Vector<DisplayInfo> configs; + status_t result = getDisplayConfigs(display, &configs); + if (result != NO_ERROR) { + return result; + } + + int activeId = getActiveConfig(display); + if (activeId < 0) { + ALOGE("No active configuration found"); + return NAME_NOT_FOUND; + } + + *info = configs[activeId]; + return NO_ERROR; +} + +int SurfaceComposerClient::getActiveConfig(const sp<IBinder>& display) { + return ComposerService::getComposerService()->getActiveConfig(display); +} + +status_t SurfaceComposerClient::setActiveConfig(const sp<IBinder>& display, int id) { + return ComposerService::getComposerService()->setActiveConfig(display, id); +} + +void SurfaceComposerClient::setDisplayPowerMode(const sp<IBinder>& token, + int mode) { + ComposerService::getComposerService()->setPowerMode(token, mode); } -void SurfaceComposerClient::blankDisplay(const sp<IBinder>& token) { - ComposerService::getComposerService()->blank(token); +status_t SurfaceComposerClient::clearAnimationFrameStats() { + return ComposerService::getComposerService()->clearAnimationFrameStats(); } -void SurfaceComposerClient::unblankDisplay(const sp<IBinder>& token) { - ComposerService::getComposerService()->unblank(token); +status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) { + return ComposerService::getComposerService()->getAnimationFrameStats(outStats); } // ---------------------------------------------------------------------------- @@ -627,12 +687,12 @@ void SurfaceComposerClient::unblankDisplay(const sp<IBinder>& token) { status_t ScreenshotClient::capture( const sp<IBinder>& display, const sp<IGraphicBufferProducer>& producer, - uint32_t reqWidth, uint32_t reqHeight, - uint32_t minLayerZ, uint32_t maxLayerZ) { + Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight, + 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); + return s->captureScreen(display, producer, sourceCrop, + reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform); } ScreenshotClient::ScreenshotClient() @@ -646,16 +706,18 @@ 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; } status_t ScreenshotClient::update(const sp<IBinder>& display, - uint32_t reqWidth, uint32_t reqHeight, - uint32_t minLayerZ, uint32_t maxLayerZ) { + Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight, + uint32_t minLayerZ, uint32_t maxLayerZ, + bool useIdentityTransform, uint32_t rotation) { sp<ISurfaceComposer> s(ComposerService::getComposerService()); if (s == NULL) return NO_INIT; sp<CpuConsumer> cpuConsumer = getCpuConsumer(); @@ -666,8 +728,9 @@ 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, sourceCrop, + reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform, + static_cast<ISurfaceComposer::Rotation>(rotation)); if (err == NO_ERROR) { err = mCpuConsumer->lockNextBuffer(&mBuffer); @@ -678,13 +741,25 @@ 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, + Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight, + uint32_t minLayerZ, uint32_t maxLayerZ, + bool useIdentityTransform) { + + return ScreenshotClient::update(display, sourceCrop, reqWidth, reqHeight, + minLayerZ, maxLayerZ, useIdentityTransform, ISurfaceComposer::eRotateNone); +} + +status_t ScreenshotClient::update(const sp<IBinder>& display, Rect sourceCrop, + bool useIdentityTransform) { + return ScreenshotClient::update(display, sourceCrop, 0, 0, 0, -1UL, + useIdentityTransform, ISurfaceComposer::eRotateNone); } -status_t ScreenshotClient::update(const sp<IBinder>& display, - uint32_t reqWidth, uint32_t reqHeight) { - return ScreenshotClient::update(display, reqWidth, reqHeight, 0, -1UL); +status_t ScreenshotClient::update(const sp<IBinder>& display, Rect sourceCrop, + uint32_t reqWidth, uint32_t reqHeight, bool useIdentityTransform) { + return ScreenshotClient::update(display, sourceCrop, reqWidth, reqHeight, + 0, -1UL, useIdentityTransform, ISurfaceComposer::eRotateNone); } void ScreenshotClient::release() { diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp index 16e533c..7597c99 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> @@ -93,68 +92,71 @@ bool SurfaceControl::isSameSurface( status_t SurfaceControl::setLayerStack(int32_t layerStack) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setLayerStack(mHandle, layerStack); + return mClient->setLayerStack(mHandle, layerStack); } status_t SurfaceControl::setLayer(int32_t layer) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setLayer(mHandle, layer); + return mClient->setLayer(mHandle, layer); } status_t SurfaceControl::setPosition(float x, float y) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setPosition(mHandle, x, y); + return mClient->setPosition(mHandle, x, y); } status_t SurfaceControl::setSize(uint32_t w, uint32_t h) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setSize(mHandle, w, h); + return mClient->setSize(mHandle, w, h); } status_t SurfaceControl::hide() { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->hide(mHandle); + return mClient->hide(mHandle); } status_t SurfaceControl::show() { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->show(mHandle); + return mClient->show(mHandle); } status_t SurfaceControl::setFlags(uint32_t flags, uint32_t mask) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setFlags(mHandle, flags, mask); + return mClient->setFlags(mHandle, flags, mask); } status_t SurfaceControl::setTransparentRegionHint(const Region& transparent) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setTransparentRegionHint(mHandle, transparent); + return mClient->setTransparentRegionHint(mHandle, transparent); } status_t SurfaceControl::setAlpha(float alpha) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setAlpha(mHandle, alpha); + return mClient->setAlpha(mHandle, alpha); } status_t SurfaceControl::setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { status_t err = validate(); if (err < 0) return err; - const sp<SurfaceComposerClient>& client(mClient); - return client->setMatrix(mHandle, dsdx, dtdx, dsdy, dtdy); + return mClient->setMatrix(mHandle, dsdx, dtdx, dsdy, dtdy); } status_t SurfaceControl::setCrop(const Rect& crop) { status_t err = validate(); if (err < 0) return err; + return mClient->setCrop(mHandle, crop); +} + +status_t SurfaceControl::clearLayerFrameStats() const { + status_t err = validate(); + if (err < 0) return err; + const sp<SurfaceComposerClient>& client(mClient); + return client->clearLayerFrameStats(mHandle); +} + +status_t SurfaceControl::getLayerFrameStats(FrameStats* outStats) const { + status_t err = validate(); + if (err < 0) return err; const sp<SurfaceComposerClient>& client(mClient); - return client->setCrop(mHandle, crop); + return client->getLayerFrameStats(mHandle, outStats); } status_t SurfaceControl::validate() const diff --git a/libs/gui/tests/Android.mk b/libs/gui/tests/Android.mk index 21bd875..e460290 100644 --- a/libs/gui/tests/Android.mk +++ b/libs/gui/tests/Android.mk @@ -9,9 +9,20 @@ 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 \ + StreamSplitter_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..c781366 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -17,55 +17,135 @@ #define LOG_TAG "BufferQueue_test" //#define LOG_NDEBUG 0 -#include <gtest/gtest.h> +#include <gui/BufferQueue.h> +#include <gui/IProducerListener.h> + +#include <ui/GraphicBuffer.h> + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> #include <utils/String8.h> #include <utils/threads.h> -#include <ui/GraphicBuffer.h> -#include <ui/FramebufferNativeWindow.h> - -#include <gui/BufferQueue.h> +#include <gtest/gtest.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<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp<IServiceManager> serviceManager = defaultServiceManager(); + serviceManager->addService(PRODUCER_NAME, producer->asBinder()); + serviceManager->addService(CONSUMER_NAME, consumer->asBinder()); + 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(new DummyProducerListener, NATIVE_WINDOW_API_CPU, false, + &qbo); + mProducer->setBufferCount(4); int slot; sp<Fence> fence; @@ -76,42 +156,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); + + int minBufferCount; + ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount)); + EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount( + minBufferCount - 1)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(0)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(-3)); - ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount( + 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); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(1)); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(2)); - ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount( + int minBufferCount; + ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount)); + + 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(new DummyProducerListener, + 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(new DummyProducerListener, + 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(newSlot, 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(new DummyProducerListener, + 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 afbc026..abd3724 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 @@ -66,11 +64,13 @@ protected: test_info->name(), params.width, params.height, params.maxLockedBuffers, params.format); - sp<BufferQueue> bq = new BufferQueue(); - mCC = new CpuConsumer(bq, params.maxLockedBuffers); + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + mCC = new CpuConsumer(consumer, params.maxLockedBuffers); String8 name("CpuConsumer_Under_Test"); mCC->setName(name); - mSTC = new Surface(bq); + mSTC = new Surface(producer); mANW = mSTC; } @@ -656,7 +656,7 @@ TEST_P(CpuConsumerTest, FromCpuLockMax) { ALOGV("Locking frame %d (too many)", params.maxLockedBuffers); CpuConsumer::LockedBuffer bTooMuch; err = mCC->lockNextBuffer(&bTooMuch); - ASSERT_TRUE(err == INVALID_OPERATION) << "Allowing too many locks"; + ASSERT_TRUE(err == NOT_ENOUGH_DATA) << "Allowing too many locks"; ALOGV("Unlocking frame 0"); err = mCC->unlockBuffer(b[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..aadfe61 --- /dev/null +++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp @@ -0,0 +1,566 @@ +/* + * 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 <gui/IProducerListener.h> + +#include <vector> + +#define ASSERT_OK(x) ASSERT_EQ(OK, (x)) +#define EXPECT_OK(x) EXPECT_EQ(OK, (x)) + +#define TEST_TOKEN ((IProducerListener*)(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()); + + mDC = new DummyConsumer; + + BufferQueue::createBufferQueue(&mProducer, &mConsumer); + + // 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<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..3a25ac5 --- /dev/null +++ b/libs/gui/tests/MultiTextureConsumer_test.cpp @@ -0,0 +1,126 @@ +/* + * 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<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + mGlConsumer = new GLConsumer(consumer, TEX_ID, + GLConsumer::TEXTURE_EXTERNAL, true, false); + mSurface = new Surface(producer); + 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..2d5b8aa --- /dev/null +++ b/libs/gui/tests/SRGB_test.cpp @@ -0,0 +1,477 @@ +/* + * 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() { + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + ASSERT_EQ(NO_ERROR, consumer->setDefaultBufferSize( + DISPLAY_WIDTH, DISPLAY_HEIGHT)); + mCpuConsumer = new CpuConsumer(consumer, 1); + String8 name("CpuConsumer_for_SRGBTest"); + mCpuConsumer->setName(name); + mInputSurface = new Surface(producer); + + 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<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/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp new file mode 100644 index 0000000..32ec90d --- /dev/null +++ b/libs/gui/tests/StreamSplitter_test.cpp @@ -0,0 +1,246 @@ +/* + * 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 "StreamSplitter_test" +//#define LOG_NDEBUG 0 + +#include <gui/BufferQueue.h> +#include <gui/IConsumerListener.h> +#include <gui/ISurfaceComposer.h> +#include <gui/StreamSplitter.h> +#include <private/gui/ComposerService.h> + +#include <gtest/gtest.h> + +namespace android { + +class StreamSplitterTest : public ::testing::Test { + +protected: + StreamSplitterTest() { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("Begin test: %s.%s", testInfo->test_case_name(), + testInfo->name()); + } + + ~StreamSplitterTest() { + const ::testing::TestInfo* const testInfo = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGV("End test: %s.%s", testInfo->test_case_name(), + testInfo->name()); + } +}; + +struct DummyListener : public BnConsumerListener { + virtual void onFrameAvailable() {} + virtual void onBuffersReleased() {} + virtual void onSidebandStreamChanged() {} +}; + +class CountedAllocator : public BnGraphicBufferAlloc { +public: + CountedAllocator() : mAllocCount(0) { + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + mAllocator = composer->createGraphicBufferAlloc(); + } + + virtual ~CountedAllocator() {} + + virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h, + PixelFormat format, uint32_t usage, status_t* error) { + ++mAllocCount; + sp<GraphicBuffer> buffer = mAllocator->createGraphicBuffer(w, h, format, + usage, error); + return buffer; + } + + int getAllocCount() const { return mAllocCount; } + +private: + sp<IGraphicBufferAlloc> mAllocator; + int mAllocCount; +}; + +TEST_F(StreamSplitterTest, OneInputOneOutput) { + sp<CountedAllocator> allocator(new CountedAllocator); + + sp<IGraphicBufferProducer> inputProducer; + sp<IGraphicBufferConsumer> inputConsumer; + BufferQueue::createBufferQueue(&inputProducer, &inputConsumer, allocator); + + sp<IGraphicBufferProducer> outputProducer; + sp<IGraphicBufferConsumer> outputConsumer; + BufferQueue::createBufferQueue(&outputProducer, &outputConsumer, allocator); + ASSERT_EQ(OK, outputConsumer->consumerConnect(new DummyListener, false)); + + sp<StreamSplitter> splitter; + status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter); + ASSERT_EQ(OK, status); + ASSERT_EQ(OK, splitter->addOutput(outputProducer)); + + IGraphicBufferProducer::QueueBufferOutput qbOutput; + ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener, + NATIVE_WINDOW_API_CPU, false, &qbOutput)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, inputProducer->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 qbInput(0, false, + Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, + Fence::NO_FENCE); + ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput)); + + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, outputConsumer->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()); + + ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mBuf, item.mFrameNumber, + EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE)); + + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ASSERT_EQ(1, allocator->getAllocCount()); +} + +TEST_F(StreamSplitterTest, OneInputMultipleOutputs) { + const int NUM_OUTPUTS = 4; + sp<CountedAllocator> allocator(new CountedAllocator); + + sp<IGraphicBufferProducer> inputProducer; + sp<IGraphicBufferConsumer> inputConsumer; + BufferQueue::createBufferQueue(&inputProducer, &inputConsumer, allocator); + + sp<IGraphicBufferProducer> outputProducers[NUM_OUTPUTS] = {}; + sp<IGraphicBufferConsumer> outputConsumers[NUM_OUTPUTS] = {}; + for (int output = 0; output < NUM_OUTPUTS; ++output) { + BufferQueue::createBufferQueue(&outputProducers[output], + &outputConsumers[output], allocator); + ASSERT_EQ(OK, outputConsumers[output]->consumerConnect( + new DummyListener, false)); + } + + sp<StreamSplitter> splitter; + status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter); + ASSERT_EQ(OK, status); + for (int output = 0; output < NUM_OUTPUTS; ++output) { + ASSERT_EQ(OK, splitter->addOutput(outputProducers[output])); + } + + IGraphicBufferProducer::QueueBufferOutput qbOutput; + ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener, + NATIVE_WINDOW_API_CPU, false, &qbOutput)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, inputProducer->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 qbInput(0, false, + Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, + Fence::NO_FENCE); + ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput)); + + for (int output = 0; output < NUM_OUTPUTS; ++output) { + IGraphicBufferConsumer::BufferItem item; + ASSERT_EQ(OK, outputConsumers[output]->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()); + + ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mBuf, + item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, + Fence::NO_FENCE)); + } + + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ASSERT_EQ(1, allocator->getAllocCount()); +} + +TEST_F(StreamSplitterTest, OutputAbandonment) { + sp<IGraphicBufferProducer> inputProducer; + sp<IGraphicBufferConsumer> inputConsumer; + BufferQueue::createBufferQueue(&inputProducer, &inputConsumer); + + sp<IGraphicBufferProducer> outputProducer; + sp<IGraphicBufferConsumer> outputConsumer; + BufferQueue::createBufferQueue(&outputProducer, &outputConsumer); + ASSERT_EQ(OK, outputConsumer->consumerConnect(new DummyListener, false)); + + sp<StreamSplitter> splitter; + status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter); + ASSERT_EQ(OK, status); + ASSERT_EQ(OK, splitter->addOutput(outputProducer)); + + IGraphicBufferProducer::QueueBufferOutput qbOutput; + ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener, + NATIVE_WINDOW_API_CPU, false, &qbOutput)); + + int slot; + sp<Fence> fence; + sp<GraphicBuffer> buffer; + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, + inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0, + GRALLOC_USAGE_SW_WRITE_OFTEN)); + ASSERT_EQ(OK, inputProducer->requestBuffer(slot, &buffer)); + + // Abandon the output + outputConsumer->consumerDisconnect(); + + IGraphicBufferProducer::QueueBufferInput qbInput(0, false, + Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, + Fence::NO_FENCE); + ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput)); + + // Input should be abandoned + ASSERT_EQ(NO_INIT, inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, + 0, GRALLOC_USAGE_SW_WRITE_OFTEN)); +} + +} // namespace android diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp index 989fcef..8cdf3bc 100644 --- a/libs/gui/tests/SurfaceTextureClient_test.cpp +++ b/libs/gui/tests/SurfaceTextureClient_test.cpp @@ -43,9 +43,12 @@ protected: ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name()); - sp<BufferQueue> bq = new BufferQueue(); - mST = new GLConsumer(bq, 123); - mSTC = new Surface(bq); + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + mST = new GLConsumer(consumer, 123, GLConsumer::TEXTURE_EXTERNAL, true, + false); + mSTC = new Surface(producer); mANW = mSTC; // We need a valid GL context so we can test updateTexImage() @@ -711,9 +714,12 @@ protected: ASSERT_NE(EGL_NO_CONTEXT, mEglContext); for (int i = 0; i < NUM_SURFACE_TEXTURES; i++) { - sp<BufferQueue> bq = new BufferQueue(); - sp<GLConsumer> st(new GLConsumer(bq, i)); - sp<Surface> stc(new Surface(bq)); + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp<GLConsumer> st(new GLConsumer(consumer, i, + GLConsumer::TEXTURE_EXTERNAL, true, false)); + sp<Surface> stc(new Surface(producer)); mEglSurfaces[i] = eglCreateWindowSurface(mEglDisplay, myConfig, static_cast<ANativeWindow*>(stc.get()), NULL); ASSERT_EQ(EGL_SUCCESS, eglGetError()); 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..53eb68c --- /dev/null +++ b/libs/gui/tests/SurfaceTextureGL.h @@ -0,0 +1,74 @@ +/* + * 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<IGraphicBufferProducer> producer; + BufferQueue::createBufferQueue(&producer, &mConsumer); + mST = new GLConsumer(mConsumer, TEX_ID, GLConsumer::TEXTURE_EXTERNAL, + true, false); + mSTC = new Surface(producer); + 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<IGraphicBufferConsumer> mConsumer; + 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..fa1e1b7 --- /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()); + mConsumer->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..1cd101e --- /dev/null +++ b/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp @@ -0,0 +1,444 @@ +/* + * 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()); +} + +TEST_F(SurfaceTextureMultiContextGLTest, + AttachAfterDisplayTerminatedSucceeds) { + 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. + ASSERT_EQ(OK, mST->releaseTexImage()); + ASSERT_EQ(OK, mST->detachFromContext()); + + // Terminate and then initialize the display. All contexts, surfaces + // and images are invalid at this point. + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay); + EGLint majorVersion = 0; + EGLint minorVersion = 0; + EXPECT_TRUE(eglTerminate(display)); + EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + // The surface is invalid so create it again. + EGLint pbufferAttribs[] = { + EGL_WIDTH, 64, + EGL_HEIGHT, 64, + EGL_NONE }; + mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig, + pbufferAttribs); + + // The second context is invalid so create it again. + 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()); + + // Now attach to and consume final frame on secondary context. + ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID)); + 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..5e6aeef 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -21,6 +21,7 @@ #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <gui/BufferItemConsumer.h> +#include <ui/Rect.h> #include <utils/String8.h> #include <private/gui/ComposerService.h> @@ -88,12 +89,14 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) { sp<ANativeWindow> anw(mSurface); // Verify the screenshot works with no protected buffers. - sp<BufferQueue> bq = new BufferQueue(); - sp<CpuConsumer> consumer = new CpuConsumer(bq, 1); + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1); sp<ISurfaceComposer> sf(ComposerService::getComposerService()); sp<IBinder> display(sf->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); - ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq, - 64, 64, 0, 0x7fffffff)); + ASSERT_EQ(NO_ERROR, sf->captureScreen(display, producer, Rect(), + 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 @@ -121,8 +124,8 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) { &buf)); ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1)); } - ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq, - 64, 64, 0, 0x7fffffff)); + ASSERT_EQ(NO_ERROR, sf->captureScreen(display, producer, Rect(), + 64, 64, 0, 0x7fffffff, false)); } TEST_F(SurfaceTest, ConcreteTypeIsSurface) { @@ -136,10 +139,12 @@ TEST_F(SurfaceTest, ConcreteTypeIsSurface) { TEST_F(SurfaceTest, QueryConsumerUsage) { const int TEST_USAGE_FLAGS = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER; - sp<BufferQueue> bq = new BufferQueue(); - sp<BufferItemConsumer> c = new BufferItemConsumer(bq, + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + sp<BufferItemConsumer> c = new BufferItemConsumer(consumer, TEST_USAGE_FLAGS); - sp<Surface> s = new Surface(bq); + sp<Surface> s = new Surface(producer); sp<ANativeWindow> anw(s); 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/Input.cpp b/libs/input/Input.cpp index 71b25b7..cd55ee5 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -21,6 +21,7 @@ #include <limits.h> #include <input/Input.h> +#include <input/InputEventLabels.h> #ifdef HAVE_ANDROID_OS #include <binder/Parcel.h> @@ -42,82 +43,12 @@ void InputEvent::initialize(const InputEvent& from) { // --- KeyEvent --- -bool KeyEvent::hasDefaultAction(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_HOME: - case AKEYCODE_BACK: - case AKEYCODE_CALL: - case AKEYCODE_ENDCALL: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_POWER: - case AKEYCODE_CAMERA: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MENU: - case AKEYCODE_NOTIFICATION: - case AKEYCODE_FOCUS: - case AKEYCODE_SEARCH: - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_MUTE: - case AKEYCODE_BRIGHTNESS_DOWN: - case AKEYCODE_BRIGHTNESS_UP: - case AKEYCODE_MEDIA_AUDIO_TRACK: - return true; - } - - return false; -} - -bool KeyEvent::hasDefaultAction() const { - return hasDefaultAction(getKeyCode()); -} - -bool KeyEvent::isSystemKey(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_MENU: - case AKEYCODE_SOFT_RIGHT: - case AKEYCODE_HOME: - case AKEYCODE_BACK: - case AKEYCODE_CALL: - case AKEYCODE_ENDCALL: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_MUTE: - case AKEYCODE_POWER: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_CAMERA: - case AKEYCODE_FOCUS: - case AKEYCODE_SEARCH: - case AKEYCODE_BRIGHTNESS_DOWN: - case AKEYCODE_BRIGHTNESS_UP: - case AKEYCODE_MEDIA_AUDIO_TRACK: - return true; - } - - return false; +const char* KeyEvent::getLabel(int32_t keyCode) { + return getLabelByKeyCode(keyCode); } -bool KeyEvent::isSystemKey() const { - return isSystemKey(getKeyCode()); +int32_t KeyEvent::getKeyCodeFromLabel(const char* label) { + return getKeyCodeByLabel(label); } void KeyEvent::initialize( @@ -592,6 +523,14 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) { return false; } +const char* MotionEvent::getLabel(int32_t axis) { + return getAxisLabel(axis); +} + +int32_t MotionEvent::getAxisFromLabel(const char* label) { + return getAxisByLabel(label); +} + // --- PooledInputEventFactory --- diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 09b2e7c..090ee53 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -22,6 +22,7 @@ #include <errno.h> #include <fcntl.h> +#include <inttypes.h> #include <math.h> #include <sys/types.h> #include <sys/socket.h> @@ -292,7 +293,7 @@ status_t InputPublisher::publishMotionEvent( float yPrecision, nsecs_t downTime, nsecs_t eventTime, - size_t pointerCount, + uint32_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) { #if DEBUG_TRANSPORT_ACTIONS @@ -300,7 +301,7 @@ status_t InputPublisher::publishMotionEvent( "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, buttonState=0x%x, " "xOffset=%f, yOffset=%f, " "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, " - "pointerCount=%d", + "pointerCount=%" PRIu32, mChannel->getName().string(), seq, deviceId, source, action, flags, edgeFlags, metaState, buttonState, xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount); @@ -312,7 +313,7 @@ status_t InputPublisher::publishMotionEvent( } if (pointerCount > MAX_POINTERS || pointerCount < 1) { - ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", + ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %" PRIu32 ".", mChannel->getName().string(), pointerCount); return BAD_VALUE; } @@ -334,7 +335,7 @@ status_t InputPublisher::publishMotionEvent( msg.body.motion.downTime = downTime; msg.body.motion.eventTime = eventTime; msg.body.motion.pointerCount = pointerCount; - for (size_t i = 0; i < pointerCount; i++) { + for (uint32_t i = 0; i < pointerCount; i++) { msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]); msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]); } @@ -654,7 +655,7 @@ void InputConsumer::updateTouchState(InputMessage* msg) { } void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) { - for (size_t i = 0; i < msg->body.motion.pointerCount; i++) { + for (uint32_t i = 0; i < msg->body.motion.pointerCount; i++) { uint32_t id = msg->body.motion.pointers[i].properties.id; if (state.lastResample.idBits.hasBit(id)) { PointerCoords& msgCoords = msg->body.motion.pointers[i].coords; @@ -894,10 +895,10 @@ void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) } void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) { - size_t pointerCount = msg->body.motion.pointerCount; + uint32_t pointerCount = msg->body.motion.pointerCount; PointerProperties pointerProperties[pointerCount]; PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { + for (uint32_t i = 0; i < pointerCount; i++) { pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties); pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords); } @@ -922,9 +923,9 @@ void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage } void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) { - size_t pointerCount = msg->body.motion.pointerCount; + uint32_t pointerCount = msg->body.motion.pointerCount; PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { + for (uint32_t i = 0; i < pointerCount; i++) { pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords); } @@ -934,7 +935,7 @@ void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) { bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) { const InputMessage& head = batch.samples.itemAt(0); - size_t pointerCount = msg->body.motion.pointerCount; + uint32_t pointerCount = msg->body.motion.pointerCount; if (head.body.motion.pointerCount != pointerCount || head.body.motion.action != msg->body.motion.action) { return false; diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 15a8774..b03e01e 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -24,6 +24,7 @@ #endif #include <android/keycodes.h> +#include <input/InputEventLabels.h> #include <input/Keyboard.h> #include <input/KeyCharacterMap.h> diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp index 0800a31..2b2f13e 100644 --- a/libs/input/KeyLayoutMap.cpp +++ b/libs/input/KeyLayoutMap.cpp @@ -19,6 +19,7 @@ #include <stdlib.h> #include <android/keycodes.h> +#include <input/InputEventLabels.h> #include <input/Keyboard.h> #include <input/KeyLayoutMap.h> #include <utils/Log.h> diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp index 7d4ac92..f4d9507 100644 --- a/libs/input/Keyboard.cpp +++ b/libs/input/Keyboard.cpp @@ -21,7 +21,7 @@ #include <limits.h> #include <input/Keyboard.h> -#include <input/KeycodeLabels.h> +#include <input/InputEventLabels.h> #include <input/KeyLayoutMap.h> #include <input/KeyCharacterMap.h> #include <input/InputDevice.h> @@ -167,46 +167,6 @@ bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier, return strstr(deviceIdentifier.name.string(), "-keypad"); } -static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) { - while (list->literal) { - if (strcmp(literal, list->literal) == 0) { - return list->value; - } - list++; - } - return list->value; -} - -static const char* lookupLabelByValue(int value, const KeycodeLabel *list) { - while (list->literal) { - if (list->value == value) { - return list->literal; - } - list++; - } - return NULL; -} - -int32_t getKeyCodeByLabel(const char* label) { - return int32_t(lookupValueByLabel(label, KEYCODES)); -} - -uint32_t getKeyFlagByLabel(const char* label) { - return uint32_t(lookupValueByLabel(label, FLAGS)); -} - -int32_t getAxisByLabel(const char* label) { - return int32_t(lookupValueByLabel(label, AXES)); -} - -const char* getAxisLabel(int32_t axisId) { - return lookupLabelByValue(axisId, AXES); -} - -int32_t getLedByLabel(const char* label) { - return int32_t(lookupValueByLabel(label, LEDS)); -} - static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) { int32_t newMetaState; if (down) { diff --git a/libs/input/tests/Android.mk b/libs/input/tests/Android.mk index c62dff1..9612a65 100644 --- a/libs/input/tests/Android.mk +++ b/libs/input/tests/Android.mk @@ -29,5 +29,16 @@ $(foreach file,$(test_src_files), \ $(eval include $(BUILD_NATIVE_TEST)) \ ) +# NOTE: This is a compile time test, and does not need to be +# run. All assertions are static_asserts and will fail during +# buildtime if something's wrong. +include $(CLEAR_VARS) +LOCAL_SRC_FILES := StructLayout_test.cpp +LOCAL_MODULE := StructLayout_test +LOCAL_CFLAGS := -std=c++11 -O0 +LOCAL_MULTILIB := both +include $(BUILD_STATIC_LIBRARY) + + # Build the manual test programs. include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 78ea98e..9105ae5 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -53,8 +53,8 @@ TEST_F(PointerCoordsTest, AxisValues) { // Set first axis. ASSERT_EQ(OK, coords.setAxisValue(1, 5)); - ASSERT_EQ(0x00000002ULL, coords.bits); ASSERT_EQ(5, coords.values[0]); + ASSERT_EQ(0x4000000000000000ULL, coords.bits); ASSERT_EQ(0, coords.getAxisValue(0)) << "getAxisValue should return zero because axis is not present"; @@ -63,7 +63,7 @@ TEST_F(PointerCoordsTest, AxisValues) { // Set an axis with a higher id than all others. (appending value at the end) ASSERT_EQ(OK, coords.setAxisValue(3, 2)); - ASSERT_EQ(0x0000000aULL, coords.bits); + ASSERT_EQ(0x5000000000000000ULL, coords.bits); ASSERT_EQ(5, coords.values[0]); ASSERT_EQ(2, coords.values[1]); @@ -78,7 +78,7 @@ TEST_F(PointerCoordsTest, AxisValues) { // Set an axis with an id lower than all others. (prepending value at beginning) ASSERT_EQ(OK, coords.setAxisValue(0, 4)); - ASSERT_EQ(0x0000000bULL, coords.bits); + ASSERT_EQ(0xd000000000000000ULL, coords.bits); ASSERT_EQ(4, coords.values[0]); ASSERT_EQ(5, coords.values[1]); ASSERT_EQ(2, coords.values[2]); @@ -94,7 +94,7 @@ TEST_F(PointerCoordsTest, AxisValues) { // Set an axis with an id between the others. (inserting value in the middle) ASSERT_EQ(OK, coords.setAxisValue(2, 1)); - ASSERT_EQ(0x0000000fULL, coords.bits); + ASSERT_EQ(0xf000000000000000ULL, coords.bits); ASSERT_EQ(4, coords.values[0]); ASSERT_EQ(5, coords.values[1]); ASSERT_EQ(1, coords.values[2]); @@ -111,7 +111,7 @@ TEST_F(PointerCoordsTest, AxisValues) { // Set an existing axis value in place. ASSERT_EQ(OK, coords.setAxisValue(1, 6)); - ASSERT_EQ(0x0000000fULL, coords.bits); + ASSERT_EQ(0xf000000000000000ULL, coords.bits); ASSERT_EQ(4, coords.values[0]); ASSERT_EQ(6, coords.values[1]); ASSERT_EQ(1, coords.values[2]); diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp new file mode 100644 index 0000000..83bc6ae --- /dev/null +++ b/libs/input/tests/StructLayout_test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 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 <input/InputTransport.h> +#include <input/Input.h> + +namespace android { + +#define CHECK_OFFSET(type, member, expected_offset) \ + static_assert((offsetof(type, member) == expected_offset), "") + +struct Foo { + uint32_t dummy; + PointerCoords coords; +}; + +void TestPointerCoordsAlignment() { + CHECK_OFFSET(Foo, coords, 8); +} + +void TestInputMessageAlignment() { + CHECK_OFFSET(InputMessage, body, 8); + + CHECK_OFFSET(InputMessage::Body::Key, seq, 0); + CHECK_OFFSET(InputMessage::Body::Key, eventTime, 8); + CHECK_OFFSET(InputMessage::Body::Key, deviceId, 16); + CHECK_OFFSET(InputMessage::Body::Key, source, 20); + CHECK_OFFSET(InputMessage::Body::Key, action, 24); + CHECK_OFFSET(InputMessage::Body::Key, flags, 28); + CHECK_OFFSET(InputMessage::Body::Key, keyCode, 32); + CHECK_OFFSET(InputMessage::Body::Key, scanCode, 36); + CHECK_OFFSET(InputMessage::Body::Key, metaState, 40); + CHECK_OFFSET(InputMessage::Body::Key, repeatCount, 44); + CHECK_OFFSET(InputMessage::Body::Key, downTime, 48); + + CHECK_OFFSET(InputMessage::Body::Motion, seq, 0); + CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8); + CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16); + CHECK_OFFSET(InputMessage::Body::Motion, source, 20); + CHECK_OFFSET(InputMessage::Body::Motion, action, 24); + CHECK_OFFSET(InputMessage::Body::Motion, flags, 28); + CHECK_OFFSET(InputMessage::Body::Motion, metaState, 32); + CHECK_OFFSET(InputMessage::Body::Motion, buttonState, 36); + CHECK_OFFSET(InputMessage::Body::Motion, edgeFlags, 40); + CHECK_OFFSET(InputMessage::Body::Motion, downTime, 48); + CHECK_OFFSET(InputMessage::Body::Motion, xOffset, 56); + CHECK_OFFSET(InputMessage::Body::Motion, yOffset, 60); + CHECK_OFFSET(InputMessage::Body::Motion, xPrecision, 64); + CHECK_OFFSET(InputMessage::Body::Motion, yPrecision, 68); + CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 72); + CHECK_OFFSET(InputMessage::Body::Motion, pointers, 80); +} + +} // namespace android diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index 008446b..eec97be 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ Fence.cpp \ FramebufferNativeWindow.cpp \ + FrameStats.cpp \ GraphicBuffer.cpp \ GraphicBufferAllocator.cpp \ GraphicBufferMapper.cpp \ diff --git a/libs/ui/Fence.cpp b/libs/ui/Fence.cpp index 93ec0ce..3c0306c 100644 --- a/libs/ui/Fence.cpp +++ b/libs/ui/Fence.cpp @@ -138,7 +138,7 @@ status_t Fence::flatten(void*& buffer, size_t& size, int*& fds, size_t& count) c if (size < getFlattenedSize() || count < getFdCount()) { return NO_MEMORY; } - FlattenableUtils::write(buffer, size, getFdCount()); + FlattenableUtils::write(buffer, size, (uint32_t)getFdCount()); if (isValid()) { *fds++ = mFenceFd; count--; @@ -156,7 +156,7 @@ status_t Fence::unflatten(void const*& buffer, size_t& size, int const*& fds, si return NO_MEMORY; } - size_t numFds; + uint32_t numFds; FlattenableUtils::read(buffer, size, numFds); if (numFds > 1) { diff --git a/libs/ui/FrameStats.cpp b/libs/ui/FrameStats.cpp new file mode 100644 index 0000000..acbe27e --- /dev/null +++ b/libs/ui/FrameStats.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 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 <ui/FrameStats.h> + +namespace android { + +bool FrameStats::isFixedSize() const { + return false; +} + +size_t FrameStats::getFlattenedSize() const { + const size_t timestampSize = sizeof(nsecs_t); + + size_t size = timestampSize; + size += 3 * desiredPresentTimesNano.size() * timestampSize; + + return size; +} + +status_t FrameStats::flatten(void* buffer, size_t size) const { + if (size < getFlattenedSize()) { + return NO_MEMORY; + } + + nsecs_t* timestamps = reinterpret_cast<nsecs_t*>(buffer); + const size_t timestampSize = sizeof(nsecs_t); + size_t frameCount = desiredPresentTimesNano.size(); + + memcpy(timestamps, &refreshPeriodNano, timestampSize); + timestamps += 1; + + memcpy(timestamps, desiredPresentTimesNano.array(), frameCount * timestampSize); + timestamps += frameCount; + + memcpy(timestamps, actualPresentTimesNano.array(), frameCount * timestampSize); + timestamps += frameCount; + + memcpy(timestamps, frameReadyTimesNano.array(), frameCount * timestampSize); + + return NO_ERROR; +} + +status_t FrameStats::unflatten(void const* buffer, size_t size) { + const size_t timestampSize = sizeof(nsecs_t); + + if (size < timestampSize) { + return NO_MEMORY; + } + + nsecs_t const* timestamps = reinterpret_cast<nsecs_t const*>(buffer); + size_t frameCount = (size - timestampSize) / (3 * timestampSize); + + memcpy(&refreshPeriodNano, timestamps, timestampSize); + timestamps += 1; + + desiredPresentTimesNano.resize(frameCount); + memcpy(desiredPresentTimesNano.editArray(), timestamps, frameCount * timestampSize); + timestamps += frameCount; + + actualPresentTimesNano.resize(frameCount); + memcpy(actualPresentTimesNano.editArray(), timestamps, frameCount * timestampSize); + timestamps += frameCount; + + frameReadyTimesNano.resize(frameCount); + memcpy(frameReadyTimesNano.editArray(), timestamps, frameCount * timestampSize); + + return NO_ERROR; +} + +} // namespace android diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp index 31a69b2..918f2e7 100644 --- a/libs/ui/FramebufferNativeWindow.cpp +++ b/libs/ui/FramebufferNativeWindow.cpp @@ -259,8 +259,8 @@ int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window, return 0; } -int FramebufferNativeWindow::lockBuffer_DEPRECATED(ANativeWindow* window, - ANativeWindowBuffer* buffer) +int FramebufferNativeWindow::lockBuffer_DEPRECATED(ANativeWindow* /*window*/, + ANativeWindowBuffer* /*buffer*/) { return NO_ERROR; } @@ -326,7 +326,7 @@ int FramebufferNativeWindow::query(const ANativeWindow* window, return BAD_VALUE; } -int FramebufferNativeWindow::perform(ANativeWindow* window, +int FramebufferNativeWindow::perform(ANativeWindow* /*window*/, int operation, ...) { switch (operation) { diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index 6c8272d..3ae8840 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -34,9 +34,17 @@ namespace android { // Buffer and implementation of ANativeWindowBuffer // =========================================================================== +static uint64_t getUniqueId() { + static volatile int32_t nextId = 0; + uint64_t id = static_cast<uint64_t>(getpid()) << 32; + id |= static_cast<uint32_t>(android_atomic_inc(&nextId)); + return id; +} + GraphicBuffer::GraphicBuffer() : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR) { + mInitCheck(NO_ERROR), mId(getUniqueId()) +{ width = height = stride = @@ -48,7 +56,7 @@ GraphicBuffer::GraphicBuffer() GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, PixelFormat reqFormat, uint32_t reqUsage) : BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR) + mInitCheck(NO_ERROR), mId(getUniqueId()) { width = height = @@ -64,7 +72,7 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, uint32_t inStride, native_handle_t* inHandle, bool keepOwnership) : BASE(), mOwner(keepOwnership ? ownHandle : ownNone), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR) + mInitCheck(NO_ERROR), mId(getUniqueId()) { width = w; height = h; @@ -77,7 +85,7 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, GraphicBuffer::GraphicBuffer(ANativeWindowBuffer* buffer, bool keepOwnership) : BASE(), mOwner(keepOwnership ? ownHandle : ownNone), mBufferMapper(GraphicBufferMapper::get()), - mInitCheck(NO_ERROR), mWrappedBuffer(buffer) + mInitCheck(NO_ERROR), mWrappedBuffer(buffer), mId(getUniqueId()) { width = buffer->width; height = buffer->height; @@ -118,6 +126,7 @@ void GraphicBuffer::dumpAllocationsToSystemLog() ANativeWindowBuffer* GraphicBuffer::getNativeBuffer() const { + LOG_ALWAYS_FATAL_IF(this == NULL, "getNativeBuffer() called on NULL GraphicBuffer"); return static_cast<ANativeWindowBuffer*>( const_cast<GraphicBuffer*>(this)); } @@ -200,8 +209,54 @@ status_t GraphicBuffer::unlock() return res; } +status_t GraphicBuffer::lockAsync(uint32_t usage, void** vaddr, int fenceFd) +{ + const Rect lockBounds(width, height); + status_t res = lockAsync(usage, lockBounds, vaddr, fenceFd); + return res; +} + +status_t GraphicBuffer::lockAsync(uint32_t usage, const Rect& rect, void** vaddr, int fenceFd) +{ + if (rect.left < 0 || rect.right > this->width || + rect.top < 0 || rect.bottom > this->height) { + ALOGE("locking pixels (%d,%d,%d,%d) outside of buffer (w=%d, h=%d)", + rect.left, rect.top, rect.right, rect.bottom, + this->width, this->height); + return BAD_VALUE; + } + status_t res = getBufferMapper().lockAsync(handle, usage, rect, vaddr, fenceFd); + return res; +} + +status_t GraphicBuffer::lockAsyncYCbCr(uint32_t usage, android_ycbcr *ycbcr, int fenceFd) +{ + const Rect lockBounds(width, height); + status_t res = lockAsyncYCbCr(usage, lockBounds, ycbcr, fenceFd); + return res; +} + +status_t GraphicBuffer::lockAsyncYCbCr(uint32_t usage, const Rect& rect, android_ycbcr *ycbcr, int fenceFd) +{ + if (rect.left < 0 || rect.right > this->width || + rect.top < 0 || rect.bottom > this->height) { + ALOGE("locking pixels (%d,%d,%d,%d) outside of buffer (w=%d, h=%d)", + rect.left, rect.top, rect.right, rect.bottom, + this->width, this->height); + return BAD_VALUE; + } + status_t res = getBufferMapper().lockAsyncYCbCr(handle, usage, rect, ycbcr, fenceFd); + return res; +} + +status_t GraphicBuffer::unlockAsync(int *fenceFd) +{ + status_t res = getBufferMapper().unlockAsync(handle, fenceFd); + return res; +} + size_t GraphicBuffer::getFlattenedSize() const { - return (8 + (handle ? handle->numInts : 0))*sizeof(int); + return (10 + (handle ? handle->numInts : 0))*sizeof(int); } size_t GraphicBuffer::getFdCount() const { @@ -215,28 +270,32 @@ status_t GraphicBuffer::flatten(void*& buffer, size_t& size, int*& fds, size_t& size_t fdCountNeeded = GraphicBuffer::getFdCount(); if (count < fdCountNeeded) return NO_MEMORY; - int* buf = static_cast<int*>(buffer); + int32_t* buf = static_cast<int32_t*>(buffer); buf[0] = 'GBFR'; buf[1] = width; buf[2] = height; buf[3] = stride; buf[4] = format; buf[5] = usage; - buf[6] = 0; - buf[7] = 0; + buf[6] = static_cast<int32_t>(mId >> 32); + buf[7] = static_cast<int32_t>(mId & 0xFFFFFFFFull); + buf[8] = 0; + buf[9] = 0; if (handle) { - buf[6] = handle->numFds; - buf[7] = handle->numInts; + buf[8] = handle->numFds; + buf[9] = handle->numInts; native_handle_t const* const h = handle; memcpy(fds, h->data, h->numFds*sizeof(int)); - memcpy(&buf[8], h->data + h->numFds, h->numInts*sizeof(int)); + memcpy(&buf[10], h->data + h->numFds, h->numInts*sizeof(int)); } 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; } @@ -248,8 +307,8 @@ status_t GraphicBuffer::unflatten( int const* buf = static_cast<int const*>(buffer); if (buf[0] != 'GBFR') return BAD_TYPE; - const size_t numFds = buf[6]; - const size_t numInts = buf[7]; + const size_t numFds = buf[8]; + const size_t numInts = buf[9]; // Limit the maxNumber to be relatively small. The number of fds or ints // should not come close to this number, and the number itself was simply @@ -264,7 +323,7 @@ status_t GraphicBuffer::unflatten( return BAD_VALUE; } - const size_t sizeNeeded = (8 + numInts) * sizeof(int); + const size_t sizeNeeded = (10 + numInts) * sizeof(int); if (size < sizeNeeded) return NO_MEMORY; size_t fdCountNeeded = numFds; @@ -289,13 +348,16 @@ status_t GraphicBuffer::unflatten( return NO_MEMORY; } memcpy(h->data, fds, numFds*sizeof(int)); - memcpy(h->data + numFds, &buf[8], numInts*sizeof(int)); + memcpy(h->data + numFds, &buf[10], numInts*sizeof(int)); handle = h; } else { width = height = stride = format = usage = 0; handle = NULL; } + mId = static_cast<uint64_t>(buf[6]) << 32; + mId |= static_cast<uint32_t>(buf[7]); + mOwner = ownHandle; if (handle != 0) { diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp index a4cfce2..320b6c0 100644 --- a/libs/ui/GraphicBufferMapper.cpp +++ b/libs/ui/GraphicBufferMapper.cpp @@ -20,6 +20,8 @@ #include <stdint.h> #include <errno.h> +#include <sync/sync.h> + #include <utils/Errors.h> #include <utils/Log.h> #include <utils/Trace.h> @@ -109,5 +111,65 @@ status_t GraphicBufferMapper::unlock(buffer_handle_t handle) return err; } +status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle, + int usage, const Rect& bounds, void** vaddr, int fenceFd) +{ + ATRACE_CALL(); + status_t err; + + if (mAllocMod->common.module_api_version >= GRALLOC_MODULE_API_VERSION_0_3) { + err = mAllocMod->lockAsync(mAllocMod, handle, usage, + bounds.left, bounds.top, bounds.width(), bounds.height(), + vaddr, fenceFd); + } else { + sync_wait(fenceFd, -1); + close(fenceFd); + err = mAllocMod->lock(mAllocMod, handle, usage, + bounds.left, bounds.top, bounds.width(), bounds.height(), + vaddr); + } + + ALOGW_IF(err, "lockAsync(...) failed %d (%s)", err, strerror(-err)); + return err; +} + +status_t GraphicBufferMapper::lockAsyncYCbCr(buffer_handle_t handle, + int usage, const Rect& bounds, android_ycbcr *ycbcr, int fenceFd) +{ + ATRACE_CALL(); + status_t err; + + if (mAllocMod->common.module_api_version >= GRALLOC_MODULE_API_VERSION_0_3) { + err = mAllocMod->lockAsync_ycbcr(mAllocMod, handle, usage, + bounds.left, bounds.top, bounds.width(), bounds.height(), + ycbcr, fenceFd); + } else { + sync_wait(fenceFd, -1); + close(fenceFd); + err = mAllocMod->lock_ycbcr(mAllocMod, handle, usage, + bounds.left, bounds.top, bounds.width(), bounds.height(), + ycbcr); + } + + ALOGW_IF(err, "lock(...) failed %d (%s)", err, strerror(-err)); + return err; +} + +status_t GraphicBufferMapper::unlockAsync(buffer_handle_t handle, int *fenceFd) +{ + ATRACE_CALL(); + status_t err; + + if (mAllocMod->common.module_api_version >= GRALLOC_MODULE_API_VERSION_0_3) { + err = mAllocMod->unlockAsync(mAllocMod, handle, fenceFd); + } else { + *fenceFd = -1; + err = mAllocMod->unlock(mAllocMod, handle); + } + + ALOGW_IF(err, "unlockAsync(...) failed %d (%s)", err, strerror(-err)); + return err; +} + // --------------------------------------------------------------------------- }; // namespace android 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 e5abcf5..fa812f4 100644 --- a/libs/ui/Region.cpp +++ b/libs/ui/Region.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "Region" +#include <inttypes.h> #include <limits.h> #include <utils/Log.h> @@ -221,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(); @@ -798,7 +815,7 @@ void Region::dump(String8& out, const char* what, uint32_t flags) const size_t SIZE = 256; char buffer[SIZE]; - snprintf(buffer, SIZE, " Region %s (this=%p, count=%d)\n", + snprintf(buffer, SIZE, " Region %s (this=%p, count=%" PRIdPTR ")\n", what, this, tail-head); out.append(buffer); while (head != tail) { @@ -814,7 +831,7 @@ void Region::dump(const char* what, uint32_t flags) const (void)flags; const_iterator head = begin(); const_iterator const tail = end(); - ALOGD(" Region %s (this=%p, count=%d)\n", what, this, tail-head); + ALOGD(" Region %s (this=%p, count=%" PRIdPTR ")\n", what, this, tail-head); while (head != tail) { ALOGD(" [%3d, %3d, %3d, %3d]\n", head->left, head->top, head->right, head->bottom); diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk index 6f62a55..b0c57db 100644 --- a/libs/ui/tests/Android.mk +++ b/libs/ui/tests/Android.mk @@ -26,8 +26,6 @@ $(foreach file,$(test_src_files), \ ) # Build the unit tests. -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) # Build the manual test programs. include $(call all-makefiles-under, $(LOCAL_PATH)) |