diff options
47 files changed, 3321 insertions, 684 deletions
diff --git a/include/gui/DisplayEventReceiver.h b/include/gui/DisplayEventReceiver.h new file mode 100644 index 0000000000..8d07c0e492 --- /dev/null +++ b/include/gui/DisplayEventReceiver.h @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#ifndef ANDROID_GUI_DISPLAY_EVENT_H +#define ANDROID_GUI_DISPLAY_EVENT_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> + +#include <binder/IInterface.h> + +// ---------------------------------------------------------------------------- + +namespace android { + +// ---------------------------------------------------------------------------- + +class BitTube; +class IDisplayEventConnection; + +// ---------------------------------------------------------------------------- + +class DisplayEventReceiver { +public: + enum { + DISPLAY_EVENT_VSYNC = 'vsyn' + }; + + struct Event { + + struct Header { + uint32_t type; + nsecs_t timestamp; + }; + + struct VSync { + uint32_t count; + }; + + Header header; + union { + VSync vsync; + }; + }; + +public: + /* + * DisplayEventReceiver creates and registers an event connection with + * SurfaceFlinger. Events start being delivered immediately. + */ + DisplayEventReceiver(); + + /* + * ~DisplayEventReceiver severs the connection with SurfaceFlinger, new events + * stop being delivered immediately. Note that the queue could have + * some events pending. These will be delivered. + */ + ~DisplayEventReceiver(); + + /* + * initCheck returns the state of DisplayEventReceiver after construction. + */ + status_t initCheck() const; + + /* + * getFd returns the file descriptor to use to receive events. + * OWNERSHIP IS RETAINED by DisplayEventReceiver. DO NOT CLOSE this + * file-descriptor. + */ + int getFd() const; + + /* + * getEvents reads event from the queue and returns how many events were + * read. Returns 0 if there are no more events or a negative error code. + * If NOT_ENOUGH_DATA is returned, the object has become invalid forever, it + * should be destroyed and getEvents() shouldn't be called again. + */ + ssize_t getEvents(Event* events, size_t count); + +private: + sp<IDisplayEventConnection> mEventConnection; + sp<BitTube> mDataChannel; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_DISPLAY_EVENT_H diff --git a/include/gui/IDisplayEventConnection.h b/include/gui/IDisplayEventConnection.h new file mode 100644 index 0000000000..8728bb5ed7 --- /dev/null +++ b/include/gui/IDisplayEventConnection.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H +#define ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> + +#include <binder/IInterface.h> + +namespace android { +// ---------------------------------------------------------------------------- + +class BitTube; + +class IDisplayEventConnection : public IInterface +{ +public: + DECLARE_META_INTERFACE(DisplayEventConnection); + + virtual sp<BitTube> getDataChannel() const = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnDisplayEventConnection : public BnInterface<IDisplayEventConnection> +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_IDISPLAY_EVENT_CONNECTION_H diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 27d863de58..a8c76725e7 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -60,10 +60,16 @@ public: virtual void onFrameAvailable() = 0; }; - // tex indicates the name OpenGL texture to which images are to be streamed. - // This texture name cannot be changed once the SurfaceTexture is created. + // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the + // name of the OpenGL ES texture to which images are to be streamed. This + // texture name cannot be changed once the SurfaceTexture is created. + // allowSynchronousMode specifies whether or not synchronous mode can be + // enabled. texTarget specifies the OpenGL ES texture target to which the + // texture will be bound in updateTexImage. useFenceSync specifies whether + // fences should be used to synchronize access to buffers if that behavior + // is enabled at compile-time. SurfaceTexture(GLuint tex, bool allowSynchronousMode = true, - GLenum texTarget = GL_TEXTURE_EXTERNAL_OES); + GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true); virtual ~SurfaceTexture(); @@ -202,6 +208,10 @@ public: // getCurrentScalingMode returns the scaling mode of the current buffer uint32_t getCurrentScalingMode() const; + // isSynchronousMode returns whether the SurfaceTexture is currently in + // synchronous mode. + bool isSynchronousMode() const; + // abandon frees all the buffers and puts the SurfaceTexture into the // 'abandoned' state. Once put in this state the SurfaceTexture can never // leave it. When in the 'abandoned' state, all methods of the @@ -272,7 +282,8 @@ private: mTransform(0), mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), mTimestamp(0), - mFrameNumber(0) { + mFrameNumber(0), + mFence(EGL_NO_SYNC_KHR) { mCrop.makeInvalid(); } @@ -345,6 +356,11 @@ private: // mFrameNumber is the number of the queued frame for this slot. uint64_t mFrameNumber; + // mFence is the EGL sync object that must signal before the buffer + // associated with this buffer slot may be dequeued. It is initialized + // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based + // on a compile-time option) set to a new sync object in updateTexImage. + EGLSyncKHR mFence; }; // mSlots is the array of buffer slots that must be mirrored on the client @@ -468,6 +484,12 @@ private: // It is set by the setName method. String8 mName; + // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync + // extension should be used to prevent buffers from being dequeued before + // it's safe for them to be written. It gets set at construction time and + // never changes. + const bool mUseFenceSync; + // mMutex is the mutex used to prevent concurrent access to the member // variables of SurfaceTexture objects. It must be locked whenever the // member variables are accessed. diff --git a/include/private/gui/ComposerService.h b/include/private/gui/ComposerService.h new file mode 100644 index 0000000000..d04491a86e --- /dev/null +++ b/include/private/gui/ComposerService.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef ANDROID_PRIVATE_GUI_COMPOSER_SERVICE_H +#define ANDROID_PRIVATE_GUI_COMPOSER_SERVICE_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Singleton.h> +#include <utils/StrongPointer.h> + + +namespace android { + +// --------------------------------------------------------------------------- + +class IMemoryHeap; +class ISurfaceComposer; +class surface_flinger_cblk_t; + +// --------------------------------------------------------------------------- + +class ComposerService : public Singleton<ComposerService> +{ + // these are constants + sp<ISurfaceComposer> mComposerService; + sp<IMemoryHeap> mServerCblkMemory; + surface_flinger_cblk_t volatile* mServerCblk; + ComposerService(); + friend class Singleton<ComposerService>; +public: + static sp<ISurfaceComposer> getComposerService(); + static surface_flinger_cblk_t const volatile * getControlBlock(); +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_PRIVATE_GUI_COMPOSER_SERVICE_H diff --git a/include/surfaceflinger/ISurfaceComposer.h b/include/surfaceflinger/ISurfaceComposer.h index 5eb09c7966..58fd89d3b6 100644 --- a/include/surfaceflinger/ISurfaceComposer.h +++ b/include/surfaceflinger/ISurfaceComposer.h @@ -33,8 +33,9 @@ namespace android { // ---------------------------------------------------------------------------- -class IMemoryHeap; class ComposerState; +class IDisplayEventConnection; +class IMemoryHeap; class ISurfaceComposer : public IInterface { @@ -124,13 +125,19 @@ public: uint32_t reqWidth, uint32_t reqHeight, uint32_t minLayerZ, uint32_t maxLayerZ) = 0; + /* triggers screen off animation */ virtual status_t turnElectronBeamOff(int32_t mode) = 0; + + /* triggers screen on animation */ virtual status_t turnElectronBeamOn(int32_t mode) = 0; /* verify that an ISurfaceTexture was created by SurfaceFlinger. */ virtual bool authenticateSurfaceTexture( const sp<ISurfaceTexture>& surface) const = 0; + + /* return an IDisplayEventConnection */ + virtual sp<IDisplayEventConnection> createDisplayEventConnection() = 0; }; // ---------------------------------------------------------------------------- @@ -151,6 +158,7 @@ public: TURN_ELECTRON_BEAM_OFF, TURN_ELECTRON_BEAM_ON, AUTHENTICATE_SURFACE, + CREATE_DISPLAY_EVENT_CONNECTION, }; virtual status_t onTransact( uint32_t code, diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h index 8226abec1c..99affdae64 100644 --- a/include/surfaceflinger/SurfaceComposerClient.h +++ b/include/surfaceflinger/SurfaceComposerClient.h @@ -28,7 +28,6 @@ #include <utils/threads.h> #include <ui/PixelFormat.h> -#include <ui/Region.h> #include <surfaceflinger/Surface.h> @@ -39,30 +38,11 @@ namespace android { class DisplayInfo; class Composer; class IMemoryHeap; -class ISurfaceComposer; +class ISurfaceComposerClient; class Region; -class surface_flinger_cblk_t; -struct layer_state_t; // --------------------------------------------------------------------------- -class ComposerService : public Singleton<ComposerService> -{ - // these are constants - sp<ISurfaceComposer> mComposerService; - sp<IMemoryHeap> mServerCblkMemory; - surface_flinger_cblk_t volatile* mServerCblk; - ComposerService(); - friend class Singleton<ComposerService>; -public: - static sp<ISurfaceComposer> getComposerService(); - static surface_flinger_cblk_t const volatile * getControlBlock(); -}; - -// --------------------------------------------------------------------------- - -class Composer; - class SurfaceComposerClient : public RefBase { friend class Composer; diff --git a/include/ui/GraphicBuffer.h b/include/ui/GraphicBuffer.h index b9deafcd3b..6ab01f4c98 100644 --- a/include/ui/GraphicBuffer.h +++ b/include/ui/GraphicBuffer.h @@ -63,6 +63,7 @@ public: USAGE_HW_RENDER = GRALLOC_USAGE_HW_RENDER, USAGE_HW_2D = GRALLOC_USAGE_HW_2D, USAGE_HW_COMPOSER = GRALLOC_USAGE_HW_COMPOSER, + USAGE_HW_VIDEO_ENCODER = GRALLOC_USAGE_HW_VIDEO_ENCODER, USAGE_HW_MASK = GRALLOC_USAGE_HW_MASK }; diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h index 2efe8ca0ee..c5bd0c5446 100755 --- a/include/ui/KeycodeLabels.h +++ b/include/ui/KeycodeLabels.h @@ -231,6 +231,10 @@ static const KeycodeLabel KEYCODES[] = { { "LANGUAGE_SWITCH", 204 }, { "MANNER_MODE", 205 }, { "3D_MODE", 206 }, + { "CONTACTS", 207 }, + { "CALENDAR", 208 }, + { "MUSIC", 209 }, + { "CALCULATOR", 210 }, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/include/utils/BasicHashtable.h b/include/utils/BasicHashtable.h new file mode 100644 index 0000000000..fdf97385f9 --- /dev/null +++ b/include/utils/BasicHashtable.h @@ -0,0 +1,393 @@ +/* + * 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. + */ + +#ifndef ANDROID_BASIC_HASHTABLE_H +#define ANDROID_BASIC_HASHTABLE_H + +#include <stdint.h> +#include <sys/types.h> +#include <utils/SharedBuffer.h> +#include <utils/TypeHelpers.h> + +namespace android { + +/* Implementation type. Nothing to see here. */ +class BasicHashtableImpl { +protected: + struct Bucket { + // The collision flag indicates that the bucket is part of a collision chain + // such that at least two entries both hash to this bucket. When true, we + // may need to seek further along the chain to find the entry. + static const uint32_t COLLISION = 0x80000000UL; + + // The present flag indicates that the bucket contains an initialized entry value. + static const uint32_t PRESENT = 0x40000000UL; + + // Mask for 30 bits worth of the hash code that are stored within the bucket to + // speed up lookups and rehashing by eliminating the need to recalculate the + // hash code of the entry's key. + static const uint32_t HASH_MASK = 0x3fffffffUL; + + // Combined value that stores the collision and present flags as well as + // a 30 bit hash code. + uint32_t cookie; + + // Storage for the entry begins here. + char entry[0]; + }; + + BasicHashtableImpl(size_t entrySize, bool hasTrivialDestructor, + size_t minimumInitialCapacity, float loadFactor); + BasicHashtableImpl(const BasicHashtableImpl& other); + + void dispose(); + + inline void edit() { + if (mBuckets && !SharedBuffer::bufferFromData(mBuckets)->onlyOwner()) { + clone(); + } + } + + void setTo(const BasicHashtableImpl& other); + void clear(); + + ssize_t next(ssize_t index) const; + ssize_t find(ssize_t index, hash_t hash, const void* __restrict__ key) const; + size_t add(hash_t hash, const void* __restrict__ entry); + void removeAt(size_t index); + void rehash(size_t minimumCapacity, float loadFactor); + + const size_t mBucketSize; // number of bytes per bucket including the entry + const bool mHasTrivialDestructor; // true if the entry type does not require destruction + size_t mCapacity; // number of buckets that can be filled before exceeding load factor + float mLoadFactor; // load factor + size_t mSize; // number of elements actually in the table + size_t mFilledBuckets; // number of buckets for which collision or present is true + size_t mBucketCount; // number of slots in the mBuckets array + void* mBuckets; // array of buckets, as a SharedBuffer + + inline const Bucket& bucketAt(const void* __restrict__ buckets, size_t index) const { + return *reinterpret_cast<const Bucket*>( + static_cast<const uint8_t*>(buckets) + index * mBucketSize); + } + + inline Bucket& bucketAt(void* __restrict__ buckets, size_t index) const { + return *reinterpret_cast<Bucket*>(static_cast<uint8_t*>(buckets) + index * mBucketSize); + } + + virtual bool compareBucketKey(const Bucket& bucket, const void* __restrict__ key) const = 0; + virtual void initializeBucketEntry(Bucket& bucket, const void* __restrict__ entry) const = 0; + virtual void destroyBucketEntry(Bucket& bucket) const = 0; + +private: + void clone(); + + // Allocates a bucket array as a SharedBuffer. + void* allocateBuckets(size_t count) const; + + // Releases a bucket array's associated SharedBuffer. + void releaseBuckets(void* __restrict__ buckets, size_t count) const; + + // Destroys the contents of buckets (invokes destroyBucketEntry for each + // populated bucket if needed). + void destroyBuckets(void* __restrict__ buckets, size_t count) const; + + // Copies the content of buckets (copies the cookie and invokes copyBucketEntry + // for each populated bucket if needed). + void copyBuckets(const void* __restrict__ fromBuckets, + void* __restrict__ toBuckets, size_t count) const; + + // Determines the appropriate size of a bucket array to store a certain minimum + // number of entries and returns its effective capacity. + static void determineCapacity(size_t minimumCapacity, float loadFactor, + size_t* __restrict__ outBucketCount, size_t* __restrict__ outCapacity); + + // Trim a hash code to 30 bits to match what we store in the bucket's cookie. + inline static hash_t trimHash(hash_t hash) { + return (hash & Bucket::HASH_MASK) ^ (hash >> 30); + } + + // Returns the index of the first bucket that is in the collision chain + // for the specified hash code, given the total number of buckets. + // (Primary hash) + inline static size_t chainStart(hash_t hash, size_t count) { + return hash % count; + } + + // Returns the increment to add to a bucket index to seek to the next bucket + // in the collision chain for the specified hash code, given the total number of buckets. + // (Secondary hash) + inline static size_t chainIncrement(hash_t hash, size_t count) { + return ((hash >> 7) | (hash << 25)) % (count - 1) + 1; + } + + // Returns the index of the next bucket that is in the collision chain + // that is defined by the specified increment, given the total number of buckets. + inline static size_t chainSeek(size_t index, size_t increment, size_t count) { + return (index + increment) % count; + } +}; + +/* + * A BasicHashtable stores entries that are indexed by hash code in place + * within an array. The basic operations are finding entries by key, + * adding new entries and removing existing entries. + * + * This class provides a very limited set of operations with simple semantics. + * It is intended to be used as a building block to construct more complex + * and interesting data structures such as HashMap. Think very hard before + * adding anything extra to BasicHashtable, it probably belongs at a + * higher level of abstraction. + * + * TKey: The key type. + * TEntry: The entry type which is what is actually stored in the array. + * + * TKey must support the following contract: + * bool operator==(const TKey& other) const; // return true if equal + * bool operator!=(const TKey& other) const; // return true if unequal + * + * TEntry must support the following contract: + * const TKey& getKey() const; // get the key from the entry + * + * This class supports storing entries with duplicate keys. Of course, it can't + * tell them apart during removal so only the first entry will be removed. + * We do this because it means that operations like add() can't fail. + */ +template <typename TKey, typename TEntry> +class BasicHashtable : private BasicHashtableImpl { +public: + /* Creates a hashtable with the specified minimum initial capacity. + * The underlying array will be created when the first entry is added. + * + * minimumInitialCapacity: The minimum initial capacity for the hashtable. + * Default is 0. + * loadFactor: The desired load factor for the hashtable, between 0 and 1. + * Default is 0.75. + */ + BasicHashtable(size_t minimumInitialCapacity = 0, float loadFactor = 0.75f); + + /* Copies a hashtable. + * The underlying storage is shared copy-on-write. + */ + BasicHashtable(const BasicHashtable& other); + + /* Clears and destroys the hashtable. + */ + virtual ~BasicHashtable(); + + /* Making this hashtable a copy of the other hashtable. + * The underlying storage is shared copy-on-write. + * + * other: The hashtable to copy. + */ + inline BasicHashtable<TKey, TEntry>& operator =(const BasicHashtable<TKey, TEntry> & other) { + setTo(other); + return *this; + } + + /* Returns the number of entries in the hashtable. + */ + inline size_t size() const { + return mSize; + } + + /* Returns the capacity of the hashtable, which is the number of elements that can + * added to the hashtable without requiring it to be grown. + */ + inline size_t capacity() const { + return mCapacity; + } + + /* Returns the number of buckets that the hashtable has, which is the size of its + * underlying array. + */ + inline size_t bucketCount() const { + return mBucketCount; + } + + /* Returns the load factor of the hashtable. */ + inline float loadFactor() const { + return mLoadFactor; + }; + + /* Returns a const reference to the entry at the specified index. + * + * index: The index of the entry to retrieve. Must be a valid index within + * the bounds of the hashtable. + */ + inline const TEntry& entryAt(size_t index) const { + return entryFor(bucketAt(mBuckets, index)); + } + + /* Returns a non-const reference to the entry at the specified index. + * + * index: The index of the entry to edit. Must be a valid index within + * the bounds of the hashtable. + */ + inline TEntry& editEntryAt(size_t index) { + edit(); + return entryFor(bucketAt(mBuckets, index)); + } + + /* Clears the hashtable. + * All entries in the hashtable are destroyed immediately. + * If you need to do something special with the entries in the hashtable then iterate + * over them and do what you need before clearing the hashtable. + */ + inline void clear() { + BasicHashtableImpl::clear(); + } + + /* Returns the index of the next entry in the hashtable given the index of a previous entry. + * If the given index is -1, then returns the index of the first entry in the hashtable, + * if there is one, or -1 otherwise. + * If the given index is not -1, then returns the index of the next entry in the hashtable, + * in strictly increasing order, or -1 if there are none left. + * + * index: The index of the previous entry that was iterated, or -1 to begin + * iteration at the beginning of the hashtable. + */ + inline ssize_t next(ssize_t index) const { + return BasicHashtableImpl::next(index); + } + + /* Finds the index of an entry with the specified key. + * If the given index is -1, then returns the index of the first matching entry, + * otherwise returns the index of the next matching entry. + * If the hashtable contains multiple entries with keys that match the requested + * key, then the sequence of entries returned is arbitrary. + * Returns -1 if no entry was found. + * + * index: The index of the previous entry with the specified key, or -1 to + * find the first matching entry. + * hash: The hashcode of the key. + * key: The key. + */ + inline ssize_t find(ssize_t index, hash_t hash, const TKey& key) const { + return BasicHashtableImpl::find(index, hash, &key); + } + + /* Adds the entry to the hashtable. + * Returns the index of the newly added entry. + * If an entry with the same key already exists, then a duplicate entry is added. + * If the entry will not fit, then the hashtable's capacity is increased and + * its contents are rehashed. See rehash(). + * + * hash: The hashcode of the key. + * entry: The entry to add. + */ + inline size_t add(hash_t hash, const TEntry& entry) { + return BasicHashtableImpl::add(hash, &entry); + } + + /* Removes the entry with the specified index from the hashtable. + * The entry is destroyed immediately. + * The index must be valid. + * + * The hashtable is not compacted after an item is removed, so it is legal + * to continue iterating over the hashtable using next() or find(). + * + * index: The index of the entry to remove. Must be a valid index within the + * bounds of the hashtable, and it must refer to an existing entry. + */ + inline void removeAt(size_t index) { + BasicHashtableImpl::removeAt(index); + } + + /* Rehashes the contents of the hashtable. + * Grows the hashtable to at least the specified minimum capacity or the + * current number of elements, whichever is larger. + * + * Rehashing causes all entries to be copied and the entry indices may change. + * Although the hash codes are cached by the hashtable, rehashing can be an + * expensive operation and should be avoided unless the hashtable's size + * needs to be changed. + * + * Rehashing is the only way to change the capacity or load factor of the + * hashtable once it has been created. It can be used to compact the + * hashtable by choosing a minimum capacity that is smaller than the current + * capacity (such as 0). + * + * minimumCapacity: The desired minimum capacity after rehashing. + * loadFactor: The desired load factor after rehashing. + */ + inline void rehash(size_t minimumCapacity, float loadFactor) { + BasicHashtableImpl::rehash(minimumCapacity, loadFactor); + } + +protected: + static inline const TEntry& entryFor(const Bucket& bucket) { + return reinterpret_cast<const TEntry&>(bucket.entry); + } + + static inline TEntry& entryFor(Bucket& bucket) { + return reinterpret_cast<TEntry&>(bucket.entry); + } + + virtual bool compareBucketKey(const Bucket& bucket, const void* __restrict__ key) const; + virtual void initializeBucketEntry(Bucket& bucket, const void* __restrict__ entry) const; + virtual void destroyBucketEntry(Bucket& bucket) const; + +private: + // For dumping the raw contents of a hashtable during testing. + friend class BasicHashtableTest; + inline uint32_t cookieAt(size_t index) const { + return bucketAt(mBuckets, index).cookie; + } +}; + +template <typename TKey, typename TEntry> +BasicHashtable<TKey, TEntry>::BasicHashtable(size_t minimumInitialCapacity, float loadFactor) : + BasicHashtableImpl(sizeof(TEntry), traits<TEntry>::has_trivial_dtor, + minimumInitialCapacity, loadFactor) { +} + +template <typename TKey, typename TEntry> +BasicHashtable<TKey, TEntry>::BasicHashtable(const BasicHashtable<TKey, TEntry>& other) : + BasicHashtableImpl(other) { +} + +template <typename TKey, typename TEntry> +BasicHashtable<TKey, TEntry>::~BasicHashtable() { + dispose(); +} + +template <typename TKey, typename TEntry> +bool BasicHashtable<TKey, TEntry>::compareBucketKey(const Bucket& bucket, + const void* __restrict__ key) const { + return entryFor(bucket).getKey() == *static_cast<const TKey*>(key); +} + +template <typename TKey, typename TEntry> +void BasicHashtable<TKey, TEntry>::initializeBucketEntry(Bucket& bucket, + const void* __restrict__ entry) const { + if (!traits<TEntry>::has_trivial_copy) { + new (&entryFor(bucket)) TEntry(*(static_cast<const TEntry*>(entry))); + } else { + memcpy(&entryFor(bucket), entry, sizeof(TEntry)); + } +} + +template <typename TKey, typename TEntry> +void BasicHashtable<TKey, TEntry>::destroyBucketEntry(Bucket& bucket) const { + if (!traits<TEntry>::has_trivial_dtor) { + entryFor(bucket).~TEntry(); + } +} + +}; // namespace android + +#endif // ANDROID_BASIC_HASHTABLE_H diff --git a/include/utils/TypeHelpers.h b/include/utils/TypeHelpers.h index a1663f30e5..7b4fb70ba6 100644 --- a/include/utils/TypeHelpers.h +++ b/include/utils/TypeHelpers.h @@ -213,6 +213,9 @@ void move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) { template <typename KEY, typename VALUE> struct key_value_pair_t { + typedef KEY key_t; + typedef VALUE value_t; + KEY key; VALUE value; key_value_pair_t() { } @@ -222,6 +225,12 @@ struct key_value_pair_t { inline bool operator < (const key_value_pair_t& o) const { return strictly_order_type(key, o.key); } + inline const KEY& getKey() const { + return key; + } + inline const VALUE& getValue() const { + return value; + } }; template<> @@ -243,6 +252,41 @@ struct trait_trivial_move< key_value_pair_t<K, V> > // --------------------------------------------------------------------------- +/* + * Hash codes. + */ +typedef uint32_t hash_t; + +template <typename TKey> +hash_t hash_type(const TKey& key); + +/* Built-in hash code specializations. + * Assumes pointers are 32bit. */ +#define ANDROID_INT32_HASH(T) \ + template <> inline hash_t hash_type(const T& value) { return hash_t(value); } +#define ANDROID_INT64_HASH(T) \ + template <> inline hash_t hash_type(const T& value) { \ + return hash_t((value >> 32) ^ value); } +#define ANDROID_REINTERPRET_HASH(T, R) \ + template <> inline hash_t hash_type(const T& value) { \ + return hash_type(*reinterpret_cast<const R*>(&value)); } + +ANDROID_INT32_HASH(bool) +ANDROID_INT32_HASH(int8_t) +ANDROID_INT32_HASH(uint8_t) +ANDROID_INT32_HASH(int16_t) +ANDROID_INT32_HASH(uint16_t) +ANDROID_INT32_HASH(int32_t) +ANDROID_INT32_HASH(uint32_t) +ANDROID_INT64_HASH(int64_t) +ANDROID_INT64_HASH(uint64_t) +ANDROID_REINTERPRET_HASH(float, uint32_t) +ANDROID_REINTERPRET_HASH(double, uint64_t) + +template <typename T> inline hash_t hash_type(const T*& value) { + return hash_type(uintptr_t(value)); +} + }; // namespace android // --------------------------------------------------------------------------- diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk index 2d716c7ed3..b8be67d446 100644 --- a/libs/gui/Android.mk +++ b/libs/gui/Android.mk @@ -3,6 +3,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ BitTube.cpp \ + DisplayEventReceiver.cpp \ + IDisplayEventConnection.cpp \ ISensorEventConnection.cpp \ ISensorServer.cpp \ ISurfaceTexture.cpp \ @@ -32,6 +34,10 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_MODULE:= libgui +ifeq ($(TARGET_BOARD_PLATFORM), tegra) + LOCAL_CFLAGS += -DALLOW_DEQUEUE_CURRENT_BUFFER +endif + include $(BUILD_SHARED_LIBRARY) ifeq (,$(ONE_SHOT_MAKEFILE)) diff --git a/libs/gui/BitTube.cpp b/libs/gui/BitTube.cpp index c632b43cd0..fa8d0eabca 100644 --- a/libs/gui/BitTube.cpp +++ b/libs/gui/BitTube.cpp @@ -97,6 +97,11 @@ ssize_t BitTube::read(void* vaddr, size_t size) len = ::read(mReceiveFd, vaddr, size); err = len < 0 ? errno : 0; } while (err == EINTR); + if (err == EAGAIN || err == EWOULDBLOCK) { + // EAGAIN means that we have non-blocking I/O but there was + // no data to be read. Nothing the client should care about. + return 0; + } return err == 0 ? len : -err; } diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp new file mode 100644 index 0000000000..3b29a113e4 --- /dev/null +++ b/libs/gui/DisplayEventReceiver.cpp @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#include <string.h> + +#include <utils/Errors.h> + +#include <gui/BitTube.h> +#include <gui/DisplayEventReceiver.h> +#include <gui/IDisplayEventConnection.h> + +#include <private/gui/ComposerService.h> + +#include <surfaceflinger/ISurfaceComposer.h> + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +DisplayEventReceiver::DisplayEventReceiver() { + sp<ISurfaceComposer> sf(ComposerService::getComposerService()); + if (sf != NULL) { + mEventConnection = sf->createDisplayEventConnection(); + if (mEventConnection != NULL) { + mDataChannel = mEventConnection->getDataChannel(); + } + } +} + +DisplayEventReceiver::~DisplayEventReceiver() { +} + +status_t DisplayEventReceiver::initCheck() const { + if (mDataChannel != NULL) + return NO_ERROR; + return NO_INIT; +} + +int DisplayEventReceiver::getFd() const { + if (mDataChannel == NULL) + return NO_INIT; + + return mDataChannel->getFd(); +} + +ssize_t DisplayEventReceiver::getEvents(DisplayEventReceiver::Event* events, + size_t count) { + ssize_t size = mDataChannel->read(events, sizeof(events[0])*count); + LOGE_IF(size<0, + "DisplayEventReceiver::getEvents error (%s)", + strerror(-size)); + if (size >= 0) { + // Note: if (size % sizeof(events[0])) != 0, we've got a + // partial read. This can happen if the queue filed up (ie: if we + // didn't pull from it fast enough). + // We discard the partial event and rely on the sender to + // re-send the event if appropriate (some events, like VSYNC + // can be lost forever). + + // returns number of events read + size /= sizeof(events[0]); + } + return size; +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/libs/gui/IDisplayEventConnection.cpp b/libs/gui/IDisplayEventConnection.cpp new file mode 100644 index 0000000000..44127fb4ac --- /dev/null +++ b/libs/gui/IDisplayEventConnection.cpp @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> + +#include <binder/Parcel.h> +#include <binder/IInterface.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/BitTube.h> + +namespace android { +// ---------------------------------------------------------------------------- + +enum { + GET_DATA_CHANNEL = IBinder::FIRST_CALL_TRANSACTION, +}; + +class BpDisplayEventConnection : public BpInterface<IDisplayEventConnection> +{ +public: + BpDisplayEventConnection(const sp<IBinder>& impl) + : BpInterface<IDisplayEventConnection>(impl) + { + } + + virtual sp<BitTube> getDataChannel() const + { + Parcel data, reply; + data.writeInterfaceToken(IDisplayEventConnection::getInterfaceDescriptor()); + remote()->transact(GET_DATA_CHANNEL, data, &reply); + return new BitTube(reply); + } +}; + +IMPLEMENT_META_INTERFACE(DisplayEventConnection, "android.gui.DisplayEventConnection"); + +// ---------------------------------------------------------------------------- + +status_t BnDisplayEventConnection::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch(code) { + case GET_DATA_CHANNEL: { + CHECK_INTERFACE(IDisplayEventConnection, data, reply); + sp<BitTube> channel(getDataChannel()); + channel->writeToParcel(reply); + return NO_ERROR; + } break; + } + return BBinder::onTransact(code, data, reply, flags); +} + +// ---------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp index 86bc62aa28..db3282781a 100644 --- a/libs/gui/ISurfaceComposer.cpp +++ b/libs/gui/ISurfaceComposer.cpp @@ -29,6 +29,9 @@ #include <surfaceflinger/ISurfaceComposer.h> +#include <gui/BitTube.h> +#include <gui/IDisplayEventConnection.h> + #include <ui/DisplayInfo.h> #include <gui/ISurfaceTexture.h> @@ -44,6 +47,8 @@ namespace android { +class IDisplayEventConnection; + class BpSurfaceComposer : public BpInterface<ISurfaceComposer> { public: @@ -174,6 +179,27 @@ public: } return result != 0; } + + virtual sp<IDisplayEventConnection> createDisplayEventConnection() + { + Parcel data, reply; + sp<IDisplayEventConnection> result; + int err = data.writeInterfaceToken( + ISurfaceComposer::getInterfaceDescriptor()); + if (err != NO_ERROR) { + return result; + } + err = remote()->transact( + BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION, + data, &reply); + if (err != NO_ERROR) { + LOGE("ISurfaceComposer::createDisplayEventConnection: error performing " + "transaction: %s (%d)", strerror(-err), -err); + return result; + } + result = interface_cast<IDisplayEventConnection>(reply.readStrongBinder()); + return result; + } }; IMPLEMENT_META_INTERFACE(SurfaceComposer, "android.ui.ISurfaceComposer"); @@ -254,6 +280,12 @@ status_t BnSurfaceComposer::onTransact( int32_t result = authenticateSurfaceTexture(surfaceTexture) ? 1 : 0; reply->writeInt32(result); } break; + case CREATE_DISPLAY_EVENT_CONNECTION: { + CHECK_INTERFACE(ISurfaceComposer, data, reply); + sp<IDisplayEventConnection> connection(createDisplayEventConnection()); + reply->writeStrongBinder(connection->asBinder()); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 4ad6c22c0d..699438c0d7 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -39,6 +39,7 @@ #include <private/surfaceflinger/LayerState.h> #include <private/surfaceflinger/SharedBufferStack.h> +#include <private/gui/ComposerService.h> namespace android { // --------------------------------------------------------------------------- diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 175de69038..6f3051ad78 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -29,6 +29,8 @@ #include <hardware/hardware.h> +#include <private/gui/ComposerService.h> + #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/SurfaceComposerClient.h> #include <surfaceflinger/IGraphicBufferAlloc.h> @@ -36,8 +38,28 @@ #include <utils/Log.h> #include <utils/String8.h> - -#define ALLOW_DEQUEUE_CURRENT_BUFFER false +// This compile option causes SurfaceTexture to return the buffer that is currently +// attached to the GL texture from dequeueBuffer when no other buffers are +// available. It requires the drivers (Gralloc, GL, OMX IL, and Camera) to do +// implicit cross-process synchronization to prevent the buffer from being +// written to before the buffer has (a) been detached from the GL texture and +// (b) all GL reads from the buffer have completed. +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER true +#warning "ALLOW_DEQUEUE_CURRENT_BUFFER enabled" +#else +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER false +#endif + +// This compile option makes SurfaceTexture use the EGL_KHR_fence_sync extension +// to synchronize access to the buffers. It will cause dequeueBuffer to stall, +// waiting for the GL reads for the buffer being dequeued to complete before +// allowing the buffer to be dequeued. +#ifdef USE_FENCE_SYNC +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#error "USE_FENCE_SYNC and ALLOW_DEQUEUE_CURRENT_BUFFER are incompatible" +#endif +#endif // Macros for including the SurfaceTexture name in log messages #define ST_LOGV(x, ...) ALOGV("[%s] "x, mName.string(), ##__VA_ARGS__) @@ -95,7 +117,7 @@ static int32_t createProcessUniqueId() { } SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, - GLenum texTarget) : + GLenum texTarget, bool useFenceSync) : mDefaultWidth(1), mDefaultHeight(1), mPixelFormat(PIXEL_FORMAT_RGBA_8888), @@ -112,6 +134,11 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, mAllowSynchronousMode(allowSynchronousMode), mConnectedApi(NO_CONNECTED_API), mAbandoned(false), +#ifdef USE_FENCE_SYNC + mUseFenceSync(useFenceSync), +#else + mUseFenceSync(false), +#endif mTexTarget(texTarget), mFrameCounter(0) { // Choose a name using the PID and a process-unique ID. @@ -257,195 +284,225 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, return BAD_VALUE; } - Mutex::Autolock lock(mMutex); - status_t returnFlags(OK); + EGLDisplay dpy = EGL_NO_DISPLAY; + EGLSyncKHR fence = EGL_NO_SYNC_KHR; - int found = -1; - int foundSync = -1; - int dequeuedCount = 0; - bool tryAgain = true; - while (tryAgain) { - if (mAbandoned) { - ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!"); - return NO_INIT; - } - - // We need to wait for the FIFO to drain if the number of buffer - // needs to change. - // - // The condition "number of buffers needs to change" is true if - // - the client doesn't care about how many buffers there are - // - AND the actual number of buffer is different from what was - // set in the last setBufferCountServer() - // - OR - - // setBufferCountServer() was set to a value incompatible with - // the synchronization mode (for instance because the sync mode - // changed since) - // - // As long as this condition is true AND the FIFO is not empty, we - // wait on mDequeueCondition. - - const int minBufferCountNeeded = mSynchronousMode ? - MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; - - const bool numberOfBuffersNeedsToChange = !mClientBufferCount && - ((mServerBufferCount != mBufferCount) || - (mServerBufferCount < minBufferCountNeeded)); - - if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) { - // wait for the FIFO to drain - mDequeueCondition.wait(mMutex); - // NOTE: we continue here because we need to reevaluate our - // whole state (eg: we could be abandoned or disconnected) - continue; - } + { // Scope for the lock + Mutex::Autolock lock(mMutex); - if (numberOfBuffersNeedsToChange) { - // here we're guaranteed that mQueue is empty - freeAllBuffersLocked(); - mBufferCount = mServerBufferCount; - if (mBufferCount < minBufferCountNeeded) - mBufferCount = minBufferCountNeeded; - mCurrentTexture = INVALID_BUFFER_SLOT; - returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; - } + int found = -1; + int foundSync = -1; + int dequeuedCount = 0; + bool tryAgain = true; + while (tryAgain) { + if (mAbandoned) { + ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } - // look for a free buffer to give to the client - found = INVALID_BUFFER_SLOT; - foundSync = INVALID_BUFFER_SLOT; - dequeuedCount = 0; - for (int i = 0; i < mBufferCount; i++) { - const int state = mSlots[i].mBufferState; - if (state == BufferSlot::DEQUEUED) { - dequeuedCount++; + // We need to wait for the FIFO to drain if the number of buffer + // needs to change. + // + // The condition "number of buffers needs to change" is true if + // - the client doesn't care about how many buffers there are + // - AND the actual number of buffer is different from what was + // set in the last setBufferCountServer() + // - OR - + // setBufferCountServer() was set to a value incompatible with + // the synchronization mode (for instance because the sync mode + // changed since) + // + // As long as this condition is true AND the FIFO is not empty, we + // wait on mDequeueCondition. + + const int minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + + const bool numberOfBuffersNeedsToChange = !mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded)); + + if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) { + // wait for the FIFO to drain + mDequeueCondition.wait(mMutex); + // NOTE: we continue here because we need to reevaluate our + // whole state (eg: we could be abandoned or disconnected) + continue; } - // if buffer is FREE it CANNOT be current - LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i), - "dequeueBuffer: buffer %d is both FREE and current!", i); + if (numberOfBuffersNeedsToChange) { + // here we're guaranteed that mQueue is empty + freeAllBuffersLocked(); + mBufferCount = mServerBufferCount; + if (mBufferCount < minBufferCountNeeded) + mBufferCount = minBufferCountNeeded; + mCurrentTexture = INVALID_BUFFER_SLOT; + returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; + } - if (ALLOW_DEQUEUE_CURRENT_BUFFER) { - if (state == BufferSlot::FREE || i == mCurrentTexture) { - foundSync = i; - if (i != mCurrentTexture) { - found = i; - break; - } + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + foundSync = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; } - } else { - if (state == BufferSlot::FREE) { - /** For Asynchronous mode, we need to return the oldest of free buffers - * There is only one instance when the Framecounter overflows, this logic - * might return the earlier buffer to client. Which is a negligible impact - **/ - if (found < 0 || mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) { + + // if buffer is FREE it CANNOT be current + LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i), + "dequeueBuffer: buffer %d is both FREE and current!", + i); + + if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) { + if (state == BufferSlot::FREE || i == mCurrentTexture) { foundSync = i; - found = i; + if (i != mCurrentTexture) { + found = i; + break; + } + } + } else { + if (state == 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. + */ + bool isOlder = mSlots[i].mFrameNumber < + mSlots[found].mFrameNumber; + if (found < 0 || isOlder) { + foundSync = i; + found = i; + } } } } - } - // clients are not allowed to dequeue more than one buffer - // if they didn't set a buffer count. - if (!mClientBufferCount && dequeuedCount) { - ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without " - "setting the buffer count"); - return -EINVAL; - } + // clients are not allowed to dequeue more than one buffer + // if they didn't set a buffer count. + if (!mClientBufferCount && 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. + bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT; + if (bufferHasBeenQueued) { + // make sure the client is not trying to dequeue more buffers + // than allowed. + const int avail = mBufferCount - (dequeuedCount+1); + if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) { + ST_LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded " + "(dequeued=%d)", + MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode), + dequeuedCount); + return -EBUSY; + } + } - // See whether a buffer has been queued since the last setBufferCount so - // we know whether to perform the MIN_UNDEQUEUED_BUFFERS check below. - bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT; - if (bufferHasBeenQueued) { - // make sure the client is not trying to dequeue more buffers - // than allowed. - const int avail = mBufferCount - (dequeuedCount+1); - if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) { - ST_LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded " - "(dequeued=%d)", - MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode), - dequeuedCount); - return -EBUSY; + // we're in synchronous mode and didn't find a buffer, we need to + // wait for some buffers to be consumed + tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT); + if (tryAgain) { + mDequeueCondition.wait(mMutex); } } - // we're in synchronous mode and didn't find a buffer, we need to wait - // for some buffers to be consumed - tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT); - if (tryAgain) { - mDequeueCondition.wait(mMutex); + if (mSynchronousMode && found == INVALID_BUFFER_SLOT) { + // foundSync guaranteed to be != INVALID_BUFFER_SLOT + found = foundSync; } - } - if (mSynchronousMode && found == INVALID_BUFFER_SLOT) { - // foundSync guaranteed to be != INVALID_BUFFER_SLOT - found = foundSync; - } + if (found == INVALID_BUFFER_SLOT) { + // This should not happen. + ST_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } - if (found == INVALID_BUFFER_SLOT) { - // This should not happen. - ST_LOGE("dequeueBuffer: no available buffer slots"); - return -EBUSY; - } + const int buf = found; + *outBuf = found; - const int buf = found; - *outBuf = found; + const bool useDefaultSize = !w && !h; + if (useDefaultSize) { + // use the default size + w = mDefaultWidth; + h = mDefaultHeight; + } - const bool useDefaultSize = !w && !h; - if (useDefaultSize) { - // use the default size - w = mDefaultWidth; - h = mDefaultHeight; - } + const bool updateFormat = (format != 0); + if (!updateFormat) { + // keep the current (or default) format + format = mPixelFormat; + } - const bool updateFormat = (format != 0); - if (!updateFormat) { - // keep the current (or default) format - format = mPixelFormat; + // buffer is now in DEQUEUED (but can also be current at the same time, + // if we're in synchronous mode) + 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)) + { + usage |= GraphicBuffer::USAGE_HW_TEXTURE; + status_t error; + sp<GraphicBuffer> graphicBuffer( + mGraphicBufferAlloc->createGraphicBuffer( + w, h, format, usage, &error)); + if (graphicBuffer == 0) { + ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer " + "failed"); + return error; + } + if (updateFormat) { + mPixelFormat = format; + } + mSlots[buf].mGraphicBuffer = graphicBuffer; + mSlots[buf].mRequestBufferCalled = false; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; + if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mSlots[buf].mEglDisplay, + mSlots[buf].mEglImage); + mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; + mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; + } + if (mCurrentTexture == buf) { + // The current texture no longer references the buffer in this slot + // since we just allocated a new buffer. + mCurrentTexture = INVALID_BUFFER_SLOT; + } + returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + } + + dpy = mSlots[buf].mEglDisplay; + fence = mSlots[buf].mFence; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; } - // buffer is now in DEQUEUED (but can also be current at the same time, - // if we're in synchronous mode) - 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)) - { - usage |= GraphicBuffer::USAGE_HW_TEXTURE; - status_t error; - sp<GraphicBuffer> graphicBuffer( - mGraphicBufferAlloc->createGraphicBuffer( - w, h, format, usage, &error)); - if (graphicBuffer == 0) { - ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer " - "failed"); - return error; - } - if (updateFormat) { - mPixelFormat = format; + if (fence != EGL_NO_SYNC_KHR) { + EGLint result = eglClientWaitSyncKHR(dpy, fence, 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) { + LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + LOGE("dequeueBuffer: timeout waiting for fence"); } - mSlots[buf].mGraphicBuffer = graphicBuffer; - mSlots[buf].mRequestBufferCalled = false; - if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(mSlots[buf].mEglDisplay, mSlots[buf].mEglImage); - mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; - mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; - } - if (mCurrentTexture == buf) { - // The current texture no longer references the buffer in this slot - // since we just allocated a new buffer. - mCurrentTexture = INVALID_BUFFER_SLOT; - } - returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + eglDestroySyncKHR(dpy, fence); } + ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", buf, mSlots[buf].mGraphicBuffer->handle, returnFlags); + return returnFlags; } @@ -642,8 +699,9 @@ status_t SurfaceTexture::disconnect(int api) { Mutex::Autolock lock(mMutex); if (mAbandoned) { - ST_LOGE("disconnect: SurfaceTexture has been abandoned!"); - return NO_INIT; + // it is not really an error to disconnect after the surface + // has been abandoned, it should just be a no-op. + return NO_ERROR; } int err = NO_ERROR; @@ -707,8 +765,8 @@ status_t SurfaceTexture::updateTexImage() { // Update the GL texture object. EGLImageKHR image = mSlots[buf].mEglImage; + EGLDisplay dpy = eglGetCurrentDisplay(); if (image == EGL_NO_IMAGE_KHR) { - EGLDisplay dpy = eglGetCurrentDisplay(); if (mSlots[buf].mGraphicBuffer == 0) { ST_LOGE("buffer at slot %d is null", buf); return BAD_VALUE; @@ -741,16 +799,32 @@ status_t SurfaceTexture::updateTexImage() { return -EINVAL; } - ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture, - mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf, - mSlots[buf].mGraphicBuffer->handle); + if (mCurrentTexture != INVALID_BUFFER_SLOT) { + if (mUseFenceSync) { + EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, + NULL); + if (fence == EGL_NO_SYNC_KHR) { + LOGE("updateTexImage: error creating fence: %#x", + eglGetError()); + return -EINVAL; + } + glFlush(); + mSlots[mCurrentTexture].mFence = fence; + } + } + + ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", + mCurrentTexture, + mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, + buf, mSlots[buf].mGraphicBuffer->handle); if (mCurrentTexture != INVALID_BUFFER_SLOT) { // The current buffer becomes FREE if it was still in the queued // state. If it has already been given to the client // (synchronous mode), then it stays in DEQUEUED state. - if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED) + if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED) { mSlots[mCurrentTexture].mBufferState = BufferSlot::FREE; + } } // Update the SurfaceTexture state. @@ -1005,6 +1079,11 @@ uint32_t SurfaceTexture::getCurrentScalingMode() const { return mCurrentScalingMode; } +bool SurfaceTexture::isSynchronousMode() const { + Mutex::Autolock lock(mMutex); + return mSynchronousMode; +} + int SurfaceTexture::query(int what, int* outValue) { Mutex::Autolock lock(mMutex); diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp index 3d47f053a5..691b52daf9 100644 --- a/libs/gui/SurfaceTextureClient.cpp +++ b/libs/gui/SurfaceTextureClient.cpp @@ -23,6 +23,8 @@ #include <utils/Log.h> +#include <private/gui/ComposerService.h> + namespace android { SurfaceTextureClient::SurfaceTextureClient( diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index 4268782220..b18e7b057e 100644 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ b/libs/gui/tests/SurfaceTexture_test.cpp @@ -396,7 +396,8 @@ protected: 1.0f, 1.0f, }; - glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, triangleVertices); + 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()); @@ -410,13 +411,17 @@ protected: // 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); + 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); + 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); + 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); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); GLfloat texMatrix[16]; @@ -531,6 +536,20 @@ void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) { } } +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; + } + } +} + TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { const int texWidth = 64; const int texHeight = 66; @@ -640,8 +659,8 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { 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()); + 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)); @@ -650,13 +669,15 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { ASSERT_TRUE(anb != NULL); sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); - ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); + ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), + buf->getNativeBuffer())); 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())); + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), + buf->getNativeBuffer())); mST->updateTexImage(); @@ -708,7 +729,8 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) { class ProducerThread : public Thread { public: - ProducerThread(const sp<ANativeWindow>& anw, const TestPixel* testPixels): + ProducerThread(const sp<ANativeWindow>& anw, + const TestPixel* testPixels): mANW(anw), mTestPixels(testPixels) { } @@ -940,21 +962,173 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35)); } -TEST_F(SurfaceTextureGLTest, TexturingFromGLFilledRGBABufferPow2) { +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 (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb) + != NO_ERROR) { + return false; + } + + // Frame 2 + if (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb) + != NO_ERROR) { + return false; + } + + // Frame 3 - error expected + mDequeueError = mANW->dequeueBuffer(mANW.get(), &anb); + return false; + } + + status_t getDequeueError() { + Mutex::Autolock lock(mMutex); + return mDequeueError; + } + + private: + sp<ANativeWindow> mANW; + status_t mDequeueError; + Mutex mMutex; + }; + + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + ASSERT_EQ(OK, mST->setSynchronousMode(true)); + ASSERT_EQ(OK, mST->setBufferCountServer(2)); + + sp<Thread> pt(new ProducerThread(mANW)); + pt->run(); + + fw->waitForFrame(); + fw->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, mANW->dequeueBuffer(mANW.get(), &anb)); + EXPECT_EQ(16, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); + EXPECT_EQ(NO_ERROR, mST->updateTexImage()); + + // make sure it works with GL_MAX_TEXTURE_SIZE + mST->setDefaultBufferSize(maxTextureSize, texHeight); + EXPECT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); + EXPECT_EQ(maxTextureSize, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); + 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, mANW->dequeueBuffer(mANW.get(), &anb)); + EXPECT_EQ(maxTextureSize+1, anb->width); + EXPECT_EQ(texHeight, anb->height); + EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); + 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(); + + EGLConfig myConfig = {0}; + EGLint numConfigs = 0; + EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig, + 1, &numConfigs)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig, + mANW.get(), NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); + + mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, + 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, TexturingFromGLFilledRGBABufferPow2) { const int texWidth = 64; const int texHeight = 64; mST->setDefaultBufferSize(texWidth, texHeight); // Do the producer side of things - EGLSurface stcEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, - mANW.get(), NULL); + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext)); ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, stcEglSurface); - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, stcEglSurface, stcEglSurface, - mEglContext)); - 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); @@ -972,7 +1146,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromGLFilledRGBABufferPow2) { glClearColor(0.0, 0.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, stcEglSurface); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); // Do the consumer side of things EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, @@ -981,12 +1155,9 @@ TEST_F(SurfaceTextureGLTest, TexturingFromGLFilledRGBABufferPow2) { glDisable(GL_SCISSOR_TEST); + mST->updateTexImage(); // Skip the first frame, which was empty mST->updateTexImage(); - // We must wait until updateTexImage has been called to destroy the - // EGLSurface because we're in synchronous mode. - eglDestroySurface(mEglDisplay, stcEglSurface); - glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); @@ -1016,90 +1187,136 @@ TEST_F(SurfaceTextureGLTest, TexturingFromGLFilledRGBABufferPow2) { EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153)); } -TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) { - class ProducerThread : public Thread { - public: - ProducerThread(const sp<ANativeWindow>& anw): - mANW(anw), - mDequeueError(NO_ERROR) { - } +TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceUnrefsBuffers) { + sp<GraphicBuffer> buffers[3]; - virtual ~ProducerThread() { - } + // 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()); - virtual bool threadLoop() { - Mutex::Autolock lock(mMutex); - ANativeWindowBuffer* anb; + 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); + eglSwapBuffers(mEglDisplay, mProducerEglSurface); - // Frame 1 - if (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - if (mANW->queueBuffer(mANW.get(), anb) - != NO_ERROR) { - return false; - } + // Consume a frame + EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + mST->updateTexImage(); + buffers[i] = mST->getCurrentBuffer(); + } - // Frame 2 - if (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { - return false; - } - if (anb == NULL) { - return false; - } - if (mANW->queueBuffer(mANW.get(), anb) - != NO_ERROR) { - return false; - } + // Destroy the GL texture object to release its ref on buffers[2]. + GLuint texID = TEX_ID; + glDeleteTextures(1, &texID); - // Frame 3 - error expected - mDequeueError = mANW->dequeueBuffer(mANW.get(), &anb); - return false; - } + // Destroy the EGLSurface + EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); - status_t getDequeueError() { - Mutex::Autolock lock(mMutex); - return mDequeueError; - } + // Release the ref that the SurfaceTexture has on buffers[2]. + mST->abandon(); - private: - sp<ANativeWindow> mANW; - status_t mDequeueError; - Mutex mMutex; - }; + EXPECT_EQ(1, buffers[0]->getStrongCount()); + EXPECT_EQ(1, buffers[1]->getStrongCount()); - sp<FrameWaiter> fw(new FrameWaiter); - mST->setFrameAvailableListener(fw); - ASSERT_EQ(OK, mST->setSynchronousMode(true)); - ASSERT_EQ(OK, mST->setBufferCountServer(2)); + // 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()); + } +} - sp<Thread> pt(new ProducerThread(mANW)); - pt->run(); +TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) { + sp<GraphicBuffer> buffers[3]; - fw->waitForFrame(); - fw->waitForFrame(); + // 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()); - // Sleep for 100ms to allow the producer thread's dequeueBuffer call to - // block waiting for a buffer to become available. - usleep(100000); + 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()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + buffers[i] = mST->getCurrentBuffer(); + } + // Abandon the SurfaceTexture, releasing the ref that the SurfaceTexture has + // on buffers[2]. mST->abandon(); - pt->requestExitAndWait(); - ASSERT_EQ(NO_INIT, - reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError()); + // 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()); + + 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, EglSurfaceDefaultsToSynchronousMode) { + // This test requires 3 buffers to run on a single thread. + mST->setBufferCountServer(3); + + ASSERT_TRUE(mST->isSynchronousMode()); + + for (int i = 0; i < 10; 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()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); + } + + ASSERT_TRUE(mST->isSynchronousMode()); } /* - * This test is for testing GL -> GL texture streaming via SurfaceTexture. It - * contains functionality to create a producer thread that will perform GL - * rendering to an ANativeWindow that feeds frames to a SurfaceTexture. - * Additionally it supports interlocking the producer and consumer threads so - * that a specific sequence of calls can be deterministically created by the - * test. + * 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 + * SurfaceTexture. 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: * @@ -1122,7 +1339,7 @@ TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) { * } * */ -class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest { +class SurfaceTextureGLThreadToGLTest : public SurfaceTextureGLToGLTest { protected: // ProducerThread is an abstract base class to simplify the creation of @@ -1223,30 +1440,8 @@ protected: Condition mFrameFinishCondition; }; - SurfaceTextureGLToGLTest(): - mProducerEglSurface(EGL_NO_SURFACE), - mProducerEglContext(EGL_NO_CONTEXT) { - } - virtual void SetUp() { - SurfaceTextureGLTest::SetUp(); - - EGLConfig myConfig = {0}; - EGLint numConfigs = 0; - EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig, - 1, &numConfigs)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - - mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig, - mANW.get(), NULL); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); - - mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, - EGL_NO_CONTEXT, getContextAttribs()); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); - + SurfaceTextureGLToGLTest::SetUp(); mFC = new FrameCondition(); mST->setFrameAvailableListener(mFC); } @@ -1255,15 +1450,9 @@ protected: if (mProducerThread != NULL) { mProducerThread->requestExitAndWait(); } - if (mProducerEglContext != EGL_NO_CONTEXT) { - eglDestroyContext(mEglDisplay, mProducerEglContext); - } - if (mProducerEglSurface != EGL_NO_SURFACE) { - eglDestroySurface(mEglDisplay, mProducerEglSurface); - } mProducerThread.clear(); mFC.clear(); - SurfaceTextureGLTest::TearDown(); + SurfaceTextureGLToGLTest::TearDown(); } void runProducerThread(const sp<ProducerThread> producerThread) { @@ -1274,13 +1463,12 @@ protected: producerThread->run(); } - EGLSurface mProducerEglSurface; - EGLContext mProducerEglContext; sp<ProducerThread> mProducerThread; sp<FrameCondition> mFC; }; -TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageBeforeFrameFinishedCompletes) { +TEST_F(SurfaceTextureGLThreadToGLTest, + UpdateTexImageBeforeFrameFinishedCompletes) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); @@ -1298,7 +1486,8 @@ TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageBeforeFrameFinishedCompletes) { // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } -TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedCompletes) { +TEST_F(SurfaceTextureGLThreadToGLTest, + UpdateTexImageAfterFrameFinishedCompletes) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); @@ -1316,7 +1505,8 @@ TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedCompletes) { // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } -TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageBeforeFrameFinishedCompletes) { +TEST_F(SurfaceTextureGLThreadToGLTest, + RepeatedUpdateTexImageBeforeFrameFinishedCompletes) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { @@ -1344,7 +1534,8 @@ TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageBeforeFrameFinishedComple } } -TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageAfterFrameFinishedCompletes) { +TEST_F(SurfaceTextureGLThreadToGLTest, + RepeatedUpdateTexImageAfterFrameFinishedCompletes) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { @@ -1373,7 +1564,8 @@ TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageAfterFrameFinishedComplet } // XXX: This test is disabled because it is currently hanging on some devices. -TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) { +TEST_F(SurfaceTextureGLThreadToGLTest, + DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) { enum { NUM_ITERATIONS = 64 }; class PT : public ProducerThread { @@ -1438,118 +1630,101 @@ TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedSwapBuffersWhileDequeueStalled } } -TEST_F(SurfaceTextureGLTest, EglDestroySurfaceUnrefsBuffers) { - EGLSurface stcEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, - mANW.get(), NULL); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, stcEglSurface); +class SurfaceTextureFBOTest : public SurfaceTextureGLTest { +protected: - sp<GraphicBuffer> buffers[3]; + virtual void SetUp() { + SurfaceTextureGLTest::SetUp(); - for (int i = 0; i < 3; i++) { - // Produce a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, stcEglSurface, stcEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - glClear(GL_COLOR_BUFFER_BIT); - eglSwapBuffers(mEglDisplay, stcEglSurface); + glGenFramebuffers(1, &mFbo); + ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError()); - // Consume a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - mST->updateTexImage(); - buffers[i] = mST->getCurrentBuffer(); + 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()); } - // Destroy the GL texture object to release its ref on buffers[2]. - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); + virtual void TearDown() { + SurfaceTextureGLTest::TearDown(); - // Destroy the EGLSurface - EXPECT_TRUE(eglDestroySurface(mEglDisplay, stcEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); + glDeleteTextures(1, &mFboTex); + glDeleteFramebuffers(1, &mFbo); + } - // Release the ref that the SurfaceTexture has on buffers[2]. - mST->abandon(); + GLuint mFbo; + GLuint mFboTex; +}; - EXPECT_EQ(1, buffers[0]->getStrongCount()); - EXPECT_EQ(1, buffers[1]->getStrongCount()); - EXPECT_EQ(1, buffers[2]->getStrongCount()); -} +// 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; -TEST_F(SurfaceTextureGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) { - EGLSurface stcEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, - mANW.get(), NULL); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, stcEglSurface); + 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)); - sp<GraphicBuffer> buffers[3]; + android_native_buffer_t* anb; + ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); + ASSERT_TRUE(anb != NULL); - for (int i = 0; i < 3; i++) { - // Produce a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, stcEglSurface, stcEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - glClear(GL_COLOR_BUFFER_BIT); - EXPECT_TRUE(eglSwapBuffers(mEglDisplay, stcEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); - // Consume a frame - EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - buffers[i] = mST->getCurrentBuffer(); - } + // 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())); - // Abandon the SurfaceTexture, releasing the ref that the SurfaceTexture has - // on buffers[2]. - mST->abandon(); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - // Destroy the GL texture object to release its ref on buffers[2]. - GLuint texID = TEX_ID; - glDeleteTextures(1, &texID); + glBindFramebuffer(GL_FRAMEBUFFER, mFbo); + drawTexture(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); - // Destroy the EGLSurface. - EXPECT_TRUE(eglDestroySurface(mEglDisplay, stcEglSurface)); - ASSERT_EQ(EGL_SUCCESS, eglGetError()); + for (int i = 0; i < 4; i++) { + SCOPED_TRACE(String8::format("frame %d", i).string()); - EXPECT_EQ(1, buffers[0]->getStrongCount()); - EXPECT_EQ(1, buffers[1]->getStrongCount()); - EXPECT_EQ(1, buffers[2]->getStrongCount()); -} + ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); + ASSERT_TRUE(anb != NULL); -TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) { - int texHeight = 16; - ANativeWindowBuffer* anb; + buf = new GraphicBuffer(anb, false); + ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), + buf->getNativeBuffer())); - GLint maxTextureSize; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + // 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())); - // make sure it works with small textures - mST->setDefaultBufferSize(16, texHeight); - EXPECT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); - EXPECT_EQ(16, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); - EXPECT_EQ(NO_ERROR, mST->updateTexImage()); + ASSERT_EQ(NO_ERROR, mST->updateTexImage()); - // make sure it works with GL_MAX_TEXTURE_SIZE - mST->setDefaultBufferSize(maxTextureSize, texHeight); - EXPECT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); - EXPECT_EQ(maxTextureSize, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); - EXPECT_EQ(NO_ERROR, mST->updateTexImage()); + drawTexture(); - // make sure it fails with GL_MAX_TEXTURE_SIZE+1 - mST->setDefaultBufferSize(maxTextureSize+1, texHeight); - EXPECT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); - EXPECT_EQ(maxTextureSize+1, anb->width); - EXPECT_EQ(texHeight, anb->height); - EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb)); - ASSERT_NE(NO_ERROR, mST->updateTexImage()); + 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/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 693b7b8248..ea527504b1 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -22,6 +22,8 @@ #include <surfaceflinger/SurfaceComposerClient.h> #include <utils/String8.h> +#include <private/gui/ComposerService.h> + namespace android { class SurfaceTest : public ::testing::Test { diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index d168d190ad..544ab744e3 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -21,6 +21,7 @@ commonSources:= \ Asset.cpp \ AssetDir.cpp \ AssetManager.cpp \ + BasicHashtable.cpp \ BlobCache.cpp \ BufferedTextOutput.cpp \ CallStack.cpp \ diff --git a/libs/utils/BasicHashtable.cpp b/libs/utils/BasicHashtable.cpp new file mode 100644 index 0000000000..fb8ec9f83f --- /dev/null +++ b/libs/utils/BasicHashtable.cpp @@ -0,0 +1,338 @@ +/* + * 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 "BasicHashtable" + +#include <math.h> + +#include <utils/Log.h> +#include <utils/BasicHashtable.h> +#include <utils/misc.h> + +namespace android { + +BasicHashtableImpl::BasicHashtableImpl(size_t entrySize, bool hasTrivialDestructor, + size_t minimumInitialCapacity, float loadFactor) : + mBucketSize(entrySize + sizeof(Bucket)), mHasTrivialDestructor(hasTrivialDestructor), + mLoadFactor(loadFactor), mSize(0), + mFilledBuckets(0), mBuckets(NULL) { + determineCapacity(minimumInitialCapacity, mLoadFactor, &mBucketCount, &mCapacity); +} + +BasicHashtableImpl::BasicHashtableImpl(const BasicHashtableImpl& other) : + mBucketSize(other.mBucketSize), mHasTrivialDestructor(other.mHasTrivialDestructor), + mCapacity(other.mCapacity), mLoadFactor(other.mLoadFactor), + mSize(other.mSize), mFilledBuckets(other.mFilledBuckets), + mBucketCount(other.mBucketCount), mBuckets(other.mBuckets) { + if (mBuckets) { + SharedBuffer::bufferFromData(mBuckets)->acquire(); + } +} + +void BasicHashtableImpl::dispose() { + if (mBuckets) { + releaseBuckets(mBuckets, mBucketCount); + } +} + +void BasicHashtableImpl::clone() { + if (mBuckets) { + void* newBuckets = allocateBuckets(mBucketCount); + copyBuckets(mBuckets, newBuckets, mBucketCount); + releaseBuckets(mBuckets, mBucketCount); + mBuckets = newBuckets; + } +} + +void BasicHashtableImpl::setTo(const BasicHashtableImpl& other) { + if (mBuckets) { + releaseBuckets(mBuckets, mBucketCount); + } + + mCapacity = other.mCapacity; + mLoadFactor = other.mLoadFactor; + mSize = other.mSize; + mFilledBuckets = other.mFilledBuckets; + mBucketCount = other.mBucketCount; + mBuckets = other.mBuckets; + + if (mBuckets) { + SharedBuffer::bufferFromData(mBuckets)->acquire(); + } +} + +void BasicHashtableImpl::clear() { + if (mBuckets) { + if (mFilledBuckets) { + SharedBuffer* sb = SharedBuffer::bufferFromData(mBuckets); + if (sb->onlyOwner()) { + destroyBuckets(mBuckets, mBucketCount); + for (size_t i = 0; i < mSize; i++) { + Bucket& bucket = bucketAt(mBuckets, i); + bucket.cookie = 0; + } + } else { + releaseBuckets(mBuckets, mBucketCount); + mBuckets = NULL; + } + mFilledBuckets = 0; + } + mSize = 0; + } +} + +ssize_t BasicHashtableImpl::next(ssize_t index) const { + if (mSize) { + while (size_t(++index) < mBucketCount) { + const Bucket& bucket = bucketAt(mBuckets, index); + if (bucket.cookie & Bucket::PRESENT) { + return index; + } + } + } + return -1; +} + +ssize_t BasicHashtableImpl::find(ssize_t index, hash_t hash, + const void* __restrict__ key) const { + if (!mSize) { + return -1; + } + + hash = trimHash(hash); + if (index < 0) { + index = chainStart(hash, mBucketCount); + + const Bucket& bucket = bucketAt(mBuckets, size_t(index)); + if (bucket.cookie & Bucket::PRESENT) { + if (compareBucketKey(bucket, key)) { + return index; + } + } else { + if (!(bucket.cookie & Bucket::COLLISION)) { + return -1; + } + } + } + + size_t inc = chainIncrement(hash, mBucketCount); + for (;;) { + index = chainSeek(index, inc, mBucketCount); + + const Bucket& bucket = bucketAt(mBuckets, size_t(index)); + if (bucket.cookie & Bucket::PRESENT) { + if ((bucket.cookie & Bucket::HASH_MASK) == hash + && compareBucketKey(bucket, key)) { + return index; + } + } + if (!(bucket.cookie & Bucket::COLLISION)) { + return -1; + } + } +} + +size_t BasicHashtableImpl::add(hash_t hash, const void* entry) { + if (!mBuckets) { + mBuckets = allocateBuckets(mBucketCount); + } else { + edit(); + } + + hash = trimHash(hash); + for (;;) { + size_t index = chainStart(hash, mBucketCount); + Bucket* bucket = &bucketAt(mBuckets, size_t(index)); + if (bucket->cookie & Bucket::PRESENT) { + size_t inc = chainIncrement(hash, mBucketCount); + do { + bucket->cookie |= Bucket::COLLISION; + index = chainSeek(index, inc, mBucketCount); + bucket = &bucketAt(mBuckets, size_t(index)); + } while (bucket->cookie & Bucket::PRESENT); + } + + uint32_t collision = bucket->cookie & Bucket::COLLISION; + if (!collision) { + if (mFilledBuckets >= mCapacity) { + rehash(mCapacity * 2, mLoadFactor); + continue; + } + mFilledBuckets += 1; + } + + bucket->cookie = collision | Bucket::PRESENT | hash; + mSize += 1; + initializeBucketEntry(*bucket, entry); + return index; + } +} + +void BasicHashtableImpl::removeAt(size_t index) { + edit(); + + Bucket& bucket = bucketAt(mBuckets, index); + bucket.cookie &= ~Bucket::PRESENT; + if (!(bucket.cookie & Bucket::COLLISION)) { + mFilledBuckets -= 1; + } + mSize -= 1; + if (!mHasTrivialDestructor) { + destroyBucketEntry(bucket); + } +} + +void BasicHashtableImpl::rehash(size_t minimumCapacity, float loadFactor) { + if (minimumCapacity < mSize) { + minimumCapacity = mSize; + } + size_t newBucketCount, newCapacity; + determineCapacity(minimumCapacity, loadFactor, &newBucketCount, &newCapacity); + + if (newBucketCount != mBucketCount || newCapacity != mCapacity) { + if (mBuckets) { + void* newBuckets; + if (mSize) { + newBuckets = allocateBuckets(newBucketCount); + for (size_t i = 0; i < mBucketCount; i++) { + const Bucket& fromBucket = bucketAt(mBuckets, i); + if (fromBucket.cookie & Bucket::PRESENT) { + hash_t hash = fromBucket.cookie & Bucket::HASH_MASK; + size_t index = chainStart(hash, newBucketCount); + Bucket* toBucket = &bucketAt(newBuckets, size_t(index)); + if (toBucket->cookie & Bucket::PRESENT) { + size_t inc = chainIncrement(hash, newBucketCount); + do { + toBucket->cookie |= Bucket::COLLISION; + index = chainSeek(index, inc, newBucketCount); + toBucket = &bucketAt(newBuckets, size_t(index)); + } while (toBucket->cookie & Bucket::PRESENT); + } + toBucket->cookie = Bucket::PRESENT | hash; + initializeBucketEntry(*toBucket, fromBucket.entry); + } + } + } else { + newBuckets = NULL; + } + releaseBuckets(mBuckets, mBucketCount); + mBuckets = newBuckets; + mFilledBuckets = mSize; + } + mBucketCount = newBucketCount; + mCapacity = newCapacity; + } + mLoadFactor = loadFactor; +} + +void* BasicHashtableImpl::allocateBuckets(size_t count) const { + size_t bytes = count * mBucketSize; + SharedBuffer* sb = SharedBuffer::alloc(bytes); + LOG_ALWAYS_FATAL_IF(!sb, "Could not allocate %u bytes for hashtable with %u buckets.", + uint32_t(bytes), uint32_t(count)); + void* buckets = sb->data(); + for (size_t i = 0; i < count; i++) { + Bucket& bucket = bucketAt(buckets, i); + bucket.cookie = 0; + } + return buckets; +} + +void BasicHashtableImpl::releaseBuckets(void* __restrict__ buckets, size_t count) const { + SharedBuffer* sb = SharedBuffer::bufferFromData(buckets); + if (sb->release(SharedBuffer::eKeepStorage) == 1) { + destroyBuckets(buckets, count); + SharedBuffer::dealloc(sb); + } +} + +void BasicHashtableImpl::destroyBuckets(void* __restrict__ buckets, size_t count) const { + if (!mHasTrivialDestructor) { + for (size_t i = 0; i < count; i++) { + Bucket& bucket = bucketAt(buckets, i); + if (bucket.cookie & Bucket::PRESENT) { + destroyBucketEntry(bucket); + } + } + } +} + +void BasicHashtableImpl::copyBuckets(const void* __restrict__ fromBuckets, + void* __restrict__ toBuckets, size_t count) const { + for (size_t i = 0; i < count; i++) { + const Bucket& fromBucket = bucketAt(fromBuckets, i); + Bucket& toBucket = bucketAt(toBuckets, i); + toBucket.cookie = fromBucket.cookie; + if (fromBucket.cookie & Bucket::PRESENT) { + initializeBucketEntry(toBucket, fromBucket.entry); + } + } +} + +// Table of 31-bit primes where each prime is no less than twice as large +// as the previous one. Generated by "primes.py". +static size_t PRIMES[] = { + 5, + 11, + 23, + 47, + 97, + 197, + 397, + 797, + 1597, + 3203, + 6421, + 12853, + 25717, + 51437, + 102877, + 205759, + 411527, + 823117, + 1646237, + 3292489, + 6584983, + 13169977, + 26339969, + 52679969, + 105359939, + 210719881, + 421439783, + 842879579, + 1685759167, + 0, +}; + +void BasicHashtableImpl::determineCapacity(size_t minimumCapacity, float loadFactor, + size_t* __restrict__ outBucketCount, size_t* __restrict__ outCapacity) { + LOG_ALWAYS_FATAL_IF(loadFactor <= 0.0f || loadFactor > 1.0f, + "Invalid load factor %0.3f. Must be in the range (0, 1].", loadFactor); + + size_t count = ceilf(minimumCapacity / loadFactor) + 1; + size_t i = 0; + while (count > PRIMES[i] && i < NELEM(PRIMES)) { + i++; + } + count = PRIMES[i]; + LOG_ALWAYS_FATAL_IF(!count, "Could not determine required number of buckets for " + "hashtable with minimum capacity %u and load factor %0.3f.", + uint32_t(minimumCapacity), loadFactor); + *outBucketCount = count; + *outCapacity = ceilf((count - 1) * loadFactor); +} + +}; // namespace android diff --git a/libs/utils/CallStack.cpp b/libs/utils/CallStack.cpp index b4c581b039..c2a5e5534f 100644 --- a/libs/utils/CallStack.cpp +++ b/libs/utils/CallStack.cpp @@ -101,17 +101,10 @@ void CallStack::dump(const char* prefix) const { get_backtrace_symbols(mStack, mCount, symbols); for (size_t i = 0; i < mCount; i++) { - const backtrace_frame_t& frame = mStack[i]; - const backtrace_symbol_t& symbol = symbols[i]; - const char* mapName = symbol.map_name ? symbol.map_name : "<unknown>"; - const char* symbolName = symbol.demangled_name ? symbol.demangled_name : symbol.name; - if (symbolName) { - LOGD("%s#%02d pc %08x %s (%s)\n", prefix, - int(i), uint32_t(symbol.relative_pc), mapName, symbolName); - } else { - LOGD("%s#%02d pc %08x %s\n", prefix, - int(i), uint32_t(symbol.relative_pc), mapName); - } + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &mStack[i], &symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + LOGD("%s%s", prefix, line); } free_backtrace_symbols(symbols, mCount); } @@ -122,17 +115,12 @@ String8 CallStack::toString(const char* prefix) const { get_backtrace_symbols(mStack, mCount, symbols); for (size_t i = 0; i < mCount; i++) { - const backtrace_frame_t& frame = mStack[i]; - const backtrace_symbol_t& symbol = symbols[i]; - const char* mapName = symbol.map_name ? symbol.map_name : "<unknown>"; - const char* symbolName = symbol.demangled_name ? symbol.demangled_name : symbol.name; - if (symbolName) { - str.appendFormat("%s#%02d pc %08x %s (%s)\n", prefix, - int(i), uint32_t(symbol.relative_pc), mapName, symbolName); - } else { - str.appendFormat("%s#%02d pc %08x %s\n", prefix, - int(i), uint32_t(symbol.relative_pc), mapName); - } + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &mStack[i], &symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + str.append(prefix); + str.append(line); + str.append("\n"); } free_backtrace_symbols(symbols, mCount); return str; diff --git a/libs/utils/primes.py b/libs/utils/primes.py new file mode 100755 index 0000000000..e161dd801e --- /dev/null +++ b/libs/utils/primes.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python2.6 +# +# 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. +# + +# +# Generates a table of prime numbers for use in BasicHashtable.cpp. +# +# Each prime is chosen such that it is a little more than twice as large as +# the previous prime in the table. This makes it easier to choose a new +# hashtable size when the underlying array is grown by as nominal factor +# of two each time. +# + +def is_odd_prime(n): + limit = (n - 1) / 2 + d = 3 + while d <= limit: + if n % d == 0: + return False + d += 2 + return True + +print "static size_t PRIMES[] = {" + +n = 5 +max = 2**31 - 1 +while n < max: + print " %d," % (n) + n = n * 2 + 1 + while not is_odd_prime(n): + n += 2 + +print " 0," +print "};" diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk index b97f52f5b8..58230f429e 100644 --- a/libs/utils/tests/Android.mk +++ b/libs/utils/tests/Android.mk @@ -4,9 +4,10 @@ include $(CLEAR_VARS) # Build the unit tests. test_src_files := \ + BasicHashtable_test.cpp \ BlobCache_test.cpp \ - ObbFile_test.cpp \ Looper_test.cpp \ + ObbFile_test.cpp \ String8_test.cpp \ Unicode_test.cpp \ ZipFileRO_test.cpp \ diff --git a/libs/utils/tests/BasicHashtable_test.cpp b/libs/utils/tests/BasicHashtable_test.cpp new file mode 100644 index 0000000000..764082dc04 --- /dev/null +++ b/libs/utils/tests/BasicHashtable_test.cpp @@ -0,0 +1,577 @@ +/* + * 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 "BasicHashtable_test" + +#include <utils/BasicHashtable.h> +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <unistd.h> + +namespace android { + +typedef int SimpleKey; +typedef int SimpleValue; +typedef key_value_pair_t<SimpleKey, SimpleValue> SimpleEntry; +typedef BasicHashtable<SimpleKey, SimpleEntry> SimpleHashtable; + +struct ComplexKey { + int k; + + explicit ComplexKey(int k) : k(k) { + instanceCount += 1; + } + + ComplexKey(const ComplexKey& other) : k(other.k) { + instanceCount += 1; + } + + ~ComplexKey() { + instanceCount -= 1; + } + + bool operator ==(const ComplexKey& other) const { + return k == other.k; + } + + bool operator !=(const ComplexKey& other) const { + return k != other.k; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexKey::instanceCount = 0; + +template<> inline hash_t hash_type(const ComplexKey& value) { + return hash_type(value.k); +} + +struct ComplexValue { + int v; + + explicit ComplexValue(int v) : v(v) { + instanceCount += 1; + } + + ComplexValue(const ComplexValue& other) : v(other.v) { + instanceCount += 1; + } + + ~ComplexValue() { + instanceCount -= 1; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexValue::instanceCount = 0; + +typedef key_value_pair_t<ComplexKey, ComplexValue> ComplexEntry; +typedef BasicHashtable<ComplexKey, ComplexEntry> ComplexHashtable; + +class BasicHashtableTest : public testing::Test { +protected: + virtual void SetUp() { + ComplexKey::instanceCount = 0; + ComplexValue::instanceCount = 0; + } + + virtual void TearDown() { + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + } + + void assertInstanceCount(ssize_t keys, ssize_t values) { + if (keys != ComplexKey::instanceCount || values != ComplexValue::instanceCount) { + FAIL() << "Expected " << keys << " keys and " << values << " values " + "but there were actually " << ComplexKey::instanceCount << " keys and " + << ComplexValue::instanceCount << " values"; + } + } + +public: + template <typename TKey, typename TEntry> + static void cookieAt(const BasicHashtable<TKey, TEntry>& h, size_t index, + bool* collision, bool* present, hash_t* hash) { + uint32_t cookie = h.cookieAt(index); + *collision = cookie & BasicHashtable<TKey, TEntry>::Bucket::COLLISION; + *present = cookie & BasicHashtable<TKey, TEntry>::Bucket::PRESENT; + *hash = cookie & BasicHashtable<TKey, TEntry>::Bucket::HASH_MASK; + } + + template <typename TKey, typename TEntry> + static const void* getBuckets(const BasicHashtable<TKey, TEntry>& h) { + return h.mBuckets; + } +}; + +template <typename TKey, typename TValue> +static size_t add(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + const TKey& key, const TValue& value) { + return h.add(hash_type(key), key_value_pair_t<TKey, TValue>(key, value)); +} + +template <typename TKey, typename TValue> +static ssize_t find(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + ssize_t index, const TKey& key) { + return h.find(index, hash_type(key), key); +} + +template <typename TKey, typename TValue> +static bool remove(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + const TKey& key) { + ssize_t index = find(h, -1, key); + if (index >= 0) { + h.removeAt(index); + return true; + } + return false; +} + +template <typename TEntry> +static void getKeyValue(const TEntry& entry, int* key, int* value); + +template <> void getKeyValue(const SimpleEntry& entry, int* key, int* value) { + *key = entry.key; + *value = entry.value; +} + +template <> void getKeyValue(const ComplexEntry& entry, int* key, int* value) { + *key = entry.key.k; + *value = entry.value.v; +} + +template <typename TKey, typename TValue> +static void dump(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h) { + LOGD("hashtable %p, size=%u, capacity=%u, bucketCount=%u", + &h, h.size(), h.capacity(), h.bucketCount()); + for (size_t i = 0; i < h.bucketCount(); i++) { + bool collision, present; + hash_t hash; + BasicHashtableTest::cookieAt(h, i, &collision, &present, &hash); + if (present) { + int key, value; + getKeyValue(h.entryAt(i), &key, &value); + LOGD(" [%3u] = collision=%d, present=%d, hash=0x%08x, key=%3d, value=%3d, " + "hash_type(key)=0x%08x", + i, collision, present, hash, key, value, hash_type(key)); + } else { + LOGD(" [%3u] = collision=%d, present=%d", + i, collision, present); + } + } +} + +TEST_F(BasicHashtableTest, DefaultConstructor_WithDefaultProperties) { + SimpleHashtable h; + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithNonUnityLoadFactor) { + SimpleHashtable h(52, 0.8f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(77U, h.capacity()); + EXPECT_EQ(97U, h.bucketCount()); + EXPECT_EQ(0.8f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithUnityLoadFactorAndExactCapacity) { + SimpleHashtable h(46, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(46U, h.capacity()); // must be one less than bucketCount because loadFactor == 1.0f + EXPECT_EQ(47U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithUnityLoadFactorAndInexactCapacity) { + SimpleHashtable h(42, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(46U, h.capacity()); // must be one less than bucketCount because loadFactor == 1.0f + EXPECT_EQ(47U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_OneEntry) { + SimpleHashtable h; + ssize_t index = find(h, -1, 8); + ASSERT_EQ(-1, index); + + index = add(h, 8, 1); + ASSERT_EQ(1U, h.size()); + + ASSERT_EQ(index, find(h, -1, 8)); + ASSERT_EQ(8, h.entryAt(index).key); + ASSERT_EQ(1, h.entryAt(index).value); + + index = find(h, index, 8); + ASSERT_EQ(-1, index); + + ASSERT_TRUE(remove(h, 8)); + ASSERT_EQ(0U, h.size()); + + index = find(h, -1, 8); + ASSERT_EQ(-1, index); +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_MultipleEntryWithUniqueKey) { + const size_t N = 11; + + SimpleHashtable h; + for (size_t i = 0; i < N; i++) { + ssize_t index = find(h, -1, int(i)); + ASSERT_EQ(-1, index); + + index = add(h, int(i), int(i * 10)); + ASSERT_EQ(i + 1, h.size()); + + ASSERT_EQ(index, find(h, -1, int(i))); + ASSERT_EQ(int(i), h.entryAt(index).key); + ASSERT_EQ(int(i * 10), h.entryAt(index).value); + + index = find(h, index, int(i)); + ASSERT_EQ(-1, index); + } + + for (size_t i = N; --i > 0; ) { + ASSERT_TRUE(remove(h, int(i))) << "i = " << i; + ASSERT_EQ(i, h.size()); + + ssize_t index = find(h, -1, int(i)); + ASSERT_EQ(-1, index); + } +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_MultipleEntryWithDuplicateKey) { + const size_t N = 11; + const int K = 1; + + SimpleHashtable h; + for (size_t i = 0; i < N; i++) { + ssize_t index = find(h, -1, K); + if (i == 0) { + ASSERT_EQ(-1, index); + } else { + ASSERT_NE(-1, index); + } + + add(h, K, int(i)); + ASSERT_EQ(i + 1, h.size()); + + index = -1; + int values = 0; + for (size_t j = 0; j <= i; j++) { + index = find(h, index, K); + ASSERT_GE(index, 0); + ASSERT_EQ(K, h.entryAt(index).key); + values |= 1 << h.entryAt(index).value; + } + ASSERT_EQ(values, (1 << (i + 1)) - 1); + + index = find(h, index, K); + ASSERT_EQ(-1, index); + } + + for (size_t i = N; --i > 0; ) { + ASSERT_TRUE(remove(h, K)) << "i = " << i; + ASSERT_EQ(i, h.size()); + + ssize_t index = -1; + for (size_t j = 0; j < i; j++) { + index = find(h, index, K); + ASSERT_GE(index, 0); + ASSERT_EQ(K, h.entryAt(index).key); + } + + index = find(h, index, K); + ASSERT_EQ(-1, index); + } +} + +TEST_F(BasicHashtableTest, Clear_WhenAlreadyEmpty_DoesNothing) { + SimpleHashtable h; + h.clear(); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Clear_AfterElementsAdded_RemovesThem) { + SimpleHashtable h; + add(h, 0, 0); + add(h, 1, 0); + h.clear(); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Clear_AfterElementsAdded_DestroysThem) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + h.clear(); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Remove_AfterElementsAdded_DestroysThem) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + ASSERT_TRUE(remove(h, ComplexKey(0))); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); + + ASSERT_TRUE(remove(h, ComplexKey(1))); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Destructor_AfterElementsAdded_DestroysThem) { + { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + } // h is destroyed here + + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Next_WhenEmpty_ReturnsMinusOne) { + SimpleHashtable h; + + ASSERT_EQ(-1, h.next(-1)); +} + +TEST_F(BasicHashtableTest, Next_WhenNonEmpty_IteratesOverAllEntries) { + const int N = 88; + + SimpleHashtable h; + for (int i = 0; i < N; i++) { + add(h, i, i * 10); + } + + bool set[N]; + memset(set, 0, sizeof(bool) * N); + int count = 0; + for (ssize_t index = -1; (index = h.next(index)) != -1; ) { + ASSERT_GE(index, 0); + ASSERT_LT(size_t(index), h.bucketCount()); + + const SimpleEntry& entry = h.entryAt(index); + ASSERT_GE(entry.key, 0); + ASSERT_LT(entry.key, N); + ASSERT_EQ(false, set[entry.key]); + ASSERT_EQ(entry.key * 10, entry.value); + + set[entry.key] = true; + count += 1; + } + ASSERT_EQ(N, count); +} + +TEST_F(BasicHashtableTest, Add_RehashesOnDemand) { + SimpleHashtable h; + size_t initialCapacity = h.capacity(); + size_t initialBucketCount = h.bucketCount(); + + for (size_t i = 0; i < initialCapacity; i++) { + add(h, int(i), 0); + } + + EXPECT_EQ(initialCapacity, h.size()); + EXPECT_EQ(initialCapacity, h.capacity()); + EXPECT_EQ(initialBucketCount, h.bucketCount()); + + add(h, -1, -1); + + EXPECT_EQ(initialCapacity + 1, h.size()); + EXPECT_GT(h.capacity(), initialCapacity); + EXPECT_GT(h.bucketCount(), initialBucketCount); + EXPECT_GT(h.bucketCount(), h.capacity()); +} + +TEST_F(BasicHashtableTest, Rehash_WhenCapacityAndBucketCountUnchanged_DoesNothing) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + const void* oldBuckets = getBuckets(h); + ASSERT_NE((void*)NULL, oldBuckets); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); + + h.rehash(h.capacity(), h.loadFactor()); + + ASSERT_EQ(oldBuckets, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenEmptyAndHasNoBuckets_ButDoesNotAllocateBuckets) { + ComplexHashtable h; + ASSERT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + h.rehash(9, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(10U, h.capacity()); + EXPECT_EQ(11U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); + EXPECT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenEmptyAndHasBuckets_ReleasesBucketsAndSetsCapacity) { + ComplexHashtable h(10); + add(h, ComplexKey(0), ComplexValue(0)); + ASSERT_TRUE(remove(h, ComplexKey(0))); + ASSERT_NE((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + h.rehash(0, 0.75f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); + EXPECT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenLessThanCurrentCapacity_ShrinksBuckets) { + ComplexHashtable h(10); + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(1)); + const void* oldBuckets = getBuckets(h); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + h.rehash(0, 0.75f); + + EXPECT_EQ(2U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); + EXPECT_NE(oldBuckets, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); +} + +TEST_F(BasicHashtableTest, CopyOnWrite) { + ComplexHashtable h1; + add(h1, ComplexKey(0), ComplexValue(0)); + add(h1, ComplexKey(1), ComplexValue(1)); + const void* originalBuckets = getBuckets(h1); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ssize_t index0 = find(h1, -1, ComplexKey(0)); + EXPECT_GE(index0, 0); + + // copy constructor acquires shared reference + ComplexHashtable h2(h1); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h2)); + EXPECT_EQ(h1.size(), h2.size()); + EXPECT_EQ(h1.capacity(), h2.capacity()); + EXPECT_EQ(h1.bucketCount(), h2.bucketCount()); + EXPECT_EQ(h1.loadFactor(), h2.loadFactor()); + EXPECT_EQ(index0, find(h2, -1, ComplexKey(0))); + + // operator= acquires shared reference + ComplexHashtable h3; + h3 = h2; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h3)); + EXPECT_EQ(h1.size(), h3.size()); + EXPECT_EQ(h1.capacity(), h3.capacity()); + EXPECT_EQ(h1.bucketCount(), h3.bucketCount()); + EXPECT_EQ(h1.loadFactor(), h3.loadFactor()); + EXPECT_EQ(index0, find(h3, -1, ComplexKey(0))); + + // editEntryAt copies shared contents + h1.editEntryAt(index0).value.v = 42; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(42, h1.entryAt(index0).value.v); + EXPECT_EQ(0, h2.entryAt(index0).value.v); + EXPECT_EQ(0, h3.entryAt(index0).value.v); + + // clear releases reference to shared contents + h2.clear(); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + EXPECT_EQ(0U, h2.size()); + ASSERT_NE(originalBuckets, getBuckets(h2)); + + // operator= acquires shared reference, destroys unshared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + EXPECT_EQ(h3.size(), h1.size()); + EXPECT_EQ(h3.capacity(), h1.capacity()); + EXPECT_EQ(h3.bucketCount(), h1.bucketCount()); + EXPECT_EQ(h3.loadFactor(), h1.loadFactor()); + EXPECT_EQ(index0, find(h1, -1, ComplexKey(0))); + + // add copies shared contents + add(h1, ComplexKey(2), ComplexValue(2)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(5, 5)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(3U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); + + // remove copies shared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + h1.removeAt(index0); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(3, 3)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(1U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); + + // rehash copies shared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + h1.rehash(10, 1.0f); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(2U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); +} + +} // namespace android diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk index 5855b635b0..9c1a10e214 100644 --- a/opengl/libs/Android.mk +++ b/opengl/libs/Android.mk @@ -44,10 +44,17 @@ ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER endif +ifneq ($(MAX_EGL_CACHE_ENTRY_SIZE),) + LOCAL_CFLAGS += -DMAX_EGL_CACHE_ENTRY_SIZE=$(MAX_EGL_CACHE_ENTRY_SIZE) +endif + +ifneq ($(MAX_EGL_CACHE_SIZE),) + LOCAL_CFLAGS += -DMAX_EGL_CACHE_SIZE=$(MAX_EGL_CACHE_SIZE) +endif + include $(BUILD_SHARED_LIBRARY) installed_libEGL := $(LOCAL_INSTALLED_MODULE) - # OpenGL drivers config file ifneq ($(BOARD_EGL_CFG),) diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp index 2237eb6c08..a63d5b054d 100644 --- a/opengl/libs/EGL/eglApi.cpp +++ b/opengl/libs/EGL/eglApi.cpp @@ -370,6 +370,11 @@ EGLSurface eglCreateWindowSurface( EGLDisplay dpy, EGLConfig config, } } + // the EGL spec requires that a new EGLSurface default to swap interval + // 1, so explicitly set that on the window here. + ANativeWindow* anw = reinterpret_cast<ANativeWindow*>(window); + anw->setSwapInterval(anw, 1); + EGLSurface surface = cnx->egl.eglCreateWindowSurface( iDpy, iConfig, window, attrib_list); if (surface != EGL_NO_SURFACE) { diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index fe32d43589..c4a7466ec5 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -25,10 +25,18 @@ #include <sys/types.h> #include <unistd.h> +#ifndef MAX_EGL_CACHE_ENTRY_SIZE +#define MAX_EGL_CACHE_ENTRY_SIZE (16 * 1024); +#endif + +#ifndef MAX_EGL_CACHE_SIZE +#define MAX_EGL_CACHE_SIZE (64 * 1024); +#endif + // Cache size limits. static const size_t maxKeySize = 1024; -static const size_t maxValueSize = 4096; -static const size_t maxTotalSize = 64 * 1024; +static const size_t maxValueSize = MAX_EGL_CACHE_ENTRY_SIZE; +static const size_t maxTotalSize = MAX_EGL_CACHE_SIZE; // Cache file header static const char* cacheFileMagic = "EGL$"; diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index 61a8358332..95d651acc2 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -2,19 +2,22 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - Layer.cpp \ - LayerBase.cpp \ - LayerDim.cpp \ - LayerScreenshot.cpp \ - DdmConnection.cpp \ - DisplayHardware/DisplayHardware.cpp \ + EventThread.cpp \ + Layer.cpp \ + LayerBase.cpp \ + LayerDim.cpp \ + LayerScreenshot.cpp \ + DdmConnection.cpp \ + DisplayHardware/DisplayHardware.cpp \ DisplayHardware/DisplayHardwareBase.cpp \ - DisplayHardware/HWComposer.cpp \ - GLExtensions.cpp \ - MessageQueue.cpp \ - SurfaceFlinger.cpp \ - SurfaceTextureLayer.cpp \ - Transform.cpp \ + DisplayHardware/HWComposer.cpp \ + DisplayHardware/VSyncBarrier.cpp \ + DisplayEventConnection.cpp \ + GLExtensions.cpp \ + MessageQueue.cpp \ + SurfaceFlinger.cpp \ + SurfaceTextureLayer.cpp \ + Transform.cpp \ LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\" @@ -28,6 +31,7 @@ ifeq ($(TARGET_BOARD_PLATFORM), omap4) endif ifeq ($(TARGET_BOARD_PLATFORM), s5pc110) LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY -DNEVER_DEFAULT_TO_ASYNC_MODE + LOCAL_CFLAGS += -DREFRESH_RATE=56 endif diff --git a/services/surfaceflinger/DisplayEventConnection.cpp b/services/surfaceflinger/DisplayEventConnection.cpp new file mode 100644 index 0000000000..a0aa9c019e --- /dev/null +++ b/services/surfaceflinger/DisplayEventConnection.cpp @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/BitTube.h> +#include <gui/DisplayEventReceiver.h> + +#include <utils/Errors.h> + +#include "SurfaceFlinger.h" +#include "DisplayEventConnection.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +DisplayEventConnection::DisplayEventConnection( + const sp<SurfaceFlinger>& flinger) + : mFlinger(flinger), mChannel(new BitTube()) +{ +} + +DisplayEventConnection::~DisplayEventConnection() { + mFlinger->cleanupDisplayEventConnection(this); +} + +void DisplayEventConnection::onFirstRef() { + // nothing to do here for now. +} + +sp<BitTube> DisplayEventConnection::getDataChannel() const { + return mChannel; +} + +status_t DisplayEventConnection::postEvent(const DisplayEventReceiver::Event& event) +{ + ssize_t size = mChannel->write(&event, sizeof(DisplayEventReceiver::Event)); + return size < 0 ? status_t(size) : status_t(NO_ERROR); +} + + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/surfaceflinger/DisplayEventConnection.h b/services/surfaceflinger/DisplayEventConnection.h new file mode 100644 index 0000000000..46cf64b4a2 --- /dev/null +++ b/services/surfaceflinger/DisplayEventConnection.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H +#define ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> + +#include <utils/Errors.h> +#include <gui/DisplayEventReceiver.h> + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +class BitTube; +class SurfaceFlinger; + +// --------------------------------------------------------------------------- + +class DisplayEventConnection : public BnDisplayEventConnection { +public: + DisplayEventConnection(const sp<SurfaceFlinger>& flinger); + + status_t postEvent(const DisplayEventReceiver::Event& event); + +private: + virtual ~DisplayEventConnection(); + virtual void onFirstRef(); + virtual sp<BitTube> getDataChannel() const; + + sp<SurfaceFlinger> const mFlinger; + sp<BitTube> const mChannel; +}; + +// --------------------------------------------------------------------------- + +}; // namespace android + +// --------------------------------------------------------------------------- + +#endif /* ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H */ diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index f94d32149b..3bbc75e6f8 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -140,6 +140,7 @@ void DisplayHardware::init(uint32_t dpy) mDpiX = mNativeWindow->xdpi; mDpiY = mNativeWindow->ydpi; mRefreshRate = fbDev->fps; + mNextFakeVSync = 0; /* FIXME: this is a temporary HACK until we are able to report the refresh rate @@ -152,6 +153,8 @@ void DisplayHardware::init(uint32_t dpy) #warning "refresh rate set via makefile to REFRESH_RATE" #endif + mRefreshPeriod = nsecs_t(1e9 / mRefreshRate); + EGLint w, h, dummy; EGLint numConfigs=0; EGLSurface surface; @@ -346,6 +349,37 @@ uint32_t DisplayHardware::getPageFlipCount() const { return mPageFlipCount; } +// this needs to be thread safe +nsecs_t DisplayHardware::waitForVSync() const { + nsecs_t timestamp; + if (mVSync.wait(×tamp) < 0) { + // vsync not supported! + usleep( getDelayToNextVSyncUs(×tamp) ); + } + return timestamp; +} + +int32_t DisplayHardware::getDelayToNextVSyncUs(nsecs_t* timestamp) const { + Mutex::Autolock _l(mFakeVSyncMutex); + const nsecs_t period = mRefreshPeriod; + const nsecs_t now = systemTime(CLOCK_MONOTONIC); + nsecs_t next_vsync = mNextFakeVSync; + nsecs_t sleep = next_vsync - now; + if (sleep < 0) { + // we missed, find where the next vsync should be + sleep = (period - ((now - next_vsync) % period)); + next_vsync = now + sleep; + } + mNextFakeVSync = next_vsync + period; + timestamp[0] = next_vsync; + + // round to next microsecond + int32_t sleep_us = (sleep + 999LL) / 1000LL; + + // guaranteed to be > 0 + return sleep_us; +} + status_t DisplayHardware::compositionComplete() const { return mNativeWindow->compositionComplete(); } diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index f02c95414f..45d4b45ad9 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -32,6 +32,7 @@ #include "GLExtensions.h" #include "DisplayHardware/DisplayHardwareBase.h" +#include "DisplayHardware/VSyncBarrier.h" namespace android { @@ -74,6 +75,9 @@ public: uint32_t getMaxTextureSize() const; uint32_t getMaxViewportDims() const; + // waits for the next vsync and returns the timestamp of when it happened + nsecs_t waitForVSync() const; + uint32_t getPageFlipCount() const; EGLDisplay getEGLDisplay() const { return mDisplay; } @@ -95,6 +99,7 @@ public: private: void init(uint32_t displayIndex) __attribute__((noinline)); void fini() __attribute__((noinline)); + int32_t getDelayToNextVSyncUs(nsecs_t* timestamp) const; sp<SurfaceFlinger> mFlinger; EGLDisplay mDisplay; @@ -112,7 +117,12 @@ private: mutable uint32_t mPageFlipCount; GLint mMaxViewportDims[2]; GLint mMaxTextureSize; - + VSyncBarrier mVSync; + + mutable Mutex mFakeVSyncMutex; + mutable nsecs_t mNextFakeVSync; + nsecs_t mRefreshPeriod; + HWComposer* mHwc; sp<FramebufferNativeWindow> mNativeWindow; diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp b/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp new file mode 100644 index 0000000000..187da203c5 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/fb.h> + +#include "DisplayHardware/VSyncBarrier.h" + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +namespace android { +// --------------------------------------------------------------------------- + +VSyncBarrier::VSyncBarrier() : mFd(-EINVAL) { +#if HAS_WAITFORVSYNC + mFd = open("/dev/graphics/fb0", O_RDWR); + if (mFd < 0) { + mFd = -errno; + } + // try to see if FBIO_WAITFORVSYNC is supported + uint32_t crt = 0; + int err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt); + if (err < 0) { + close(mFd); + mFd = -EINVAL; + } +#endif +} + +VSyncBarrier::~VSyncBarrier() { + if (mFd >= 0) { + close(mFd); + } +} + +status_t VSyncBarrier::initCheck() const { + return mFd < 0 ? mFd : status_t(NO_ERROR); +} + +// this must be thread-safe +status_t VSyncBarrier::wait(nsecs_t* timestamp) const { + if (mFd < 0) { + return mFd; + } + + int err; + uint32_t crt = 0; + do { + err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt); + } while (err<0 && errno==EINTR); + if (err < 0) { + return -errno; + } + // ideally this would come from the driver + timestamp[0] = systemTime(); + return NO_ERROR; +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.h b/services/surfaceflinger/DisplayHardware/VSyncBarrier.h new file mode 100644 index 0000000000..3c3295050a --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/VSyncBarrier.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#ifndef ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ +#define ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/Timers.h> + +namespace android { +// --------------------------------------------------------------------------- + +class VSyncBarrier { + int mFd; +public: + VSyncBarrier(); + ~VSyncBarrier(); + status_t initCheck() const; + status_t wait(nsecs_t* timestamp) const; +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif /* ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ */ diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp new file mode 100644 index 0000000000..edb06ba21a --- /dev/null +++ b/services/surfaceflinger/EventThread.cpp @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/DisplayEventReceiver.h> + +#include <utils/Errors.h> + +#include "DisplayHardware/DisplayHardware.h" +#include "DisplayEventConnection.h" +#include "EventThread.h" +#include "SurfaceFlinger.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +EventThread::EventThread(const sp<SurfaceFlinger>& flinger) + : mFlinger(flinger), + mHw(flinger->graphicPlane(0).displayHardware()), + mDeliveredEvents(0) +{ +} + +void EventThread::onFirstRef() { + run("EventThread", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); +} + +status_t EventThread::registerDisplayEventConnection( + const sp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + mDisplayEventConnections.add(connection); + mCondition.signal(); + return NO_ERROR; +} + +status_t EventThread::unregisterDisplayEventConnection( + const wp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + mDisplayEventConnections.remove(connection); + mCondition.signal(); + return NO_ERROR; +} + +bool EventThread::threadLoop() { + + nsecs_t timestamp; + Mutex::Autolock _l(mLock); + do { + // wait for listeners + while (!mDisplayEventConnections.size()) { + mCondition.wait(mLock); + } + + // wait for vsync + mLock.unlock(); + timestamp = mHw.waitForVSync(); + mLock.lock(); + + // make sure we still have some listeners + } while (!mDisplayEventConnections.size()); + + + // dispatch vsync events to listeners... + mDeliveredEvents++; + const size_t count = mDisplayEventConnections.size(); + + DisplayEventReceiver::Event vsync; + vsync.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC; + vsync.header.timestamp = timestamp; + vsync.vsync.count = mDeliveredEvents; + + for (size_t i=0 ; i<count ; i++) { + sp<DisplayEventConnection> conn(mDisplayEventConnections.itemAt(i).promote()); + // make sure the connection didn't die + if (conn != NULL) { + status_t err = conn->postEvent(vsync); + if (err == -EAGAIN || err == -EWOULDBLOCK) { + // The destination doesn't accept events anymore, it's probably + // full. For now, we just drop the events on the floor. + // Note that some events cannot be dropped and would have to be + // re-sent later. Right-now we don't have the ability to do + // this, but it doesn't matter for VSYNC. + } else if (err < 0) { + // handle any other error on the pipe as fatal. the only + // reasonable thing to do is to clean-up this connection. + // The most common error we'll get here is -EPIPE. + mDisplayEventConnections.remove(conn); + } + } + } + + return true; +} + +status_t EventThread::readyToRun() { + LOGI("EventThread ready to run."); + return NO_ERROR; +} + +void EventThread::dump(String8& result, char* buffer, size_t SIZE) const { + Mutex::Autolock _l(mLock); + result.append("VSYNC state:\n"); + snprintf(buffer, SIZE, " numListeners=%u, events-delivered: %u\n", + mDisplayEventConnections.size(), mDeliveredEvents); + result.append(buffer); +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/surfaceflinger/EventThread.h b/services/surfaceflinger/EventThread.h new file mode 100644 index 0000000000..0482ab7528 --- /dev/null +++ b/services/surfaceflinger/EventThread.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#ifndef ANDROID_SURFACE_FLINGER_EVENT_THREAD_H +#define ANDROID_SURFACE_FLINGER_EVENT_THREAD_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> + +#include <utils/Errors.h> +#include <utils/threads.h> +#include <utils/SortedVector.h> + +#include "DisplayEventConnection.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +class SurfaceFlinger; +class DisplayHardware; + +// --------------------------------------------------------------------------- + +class EventThread : public Thread { + friend class DisplayEventConnection; + +public: + EventThread(const sp<SurfaceFlinger>& flinger); + + status_t registerDisplayEventConnection( + const sp<DisplayEventConnection>& connection); + + status_t unregisterDisplayEventConnection( + const wp<DisplayEventConnection>& connection); + + void dump(String8& result, char* buffer, size_t SIZE) const; + +private: + virtual bool threadLoop(); + virtual status_t readyToRun(); + virtual void onFirstRef(); + + // constants + sp<SurfaceFlinger> mFlinger; + const DisplayHardware& mHw; + + mutable Mutex mLock; + mutable Condition mCondition; + + // protected by mLock + SortedVector<wp<DisplayEventConnection> > mDisplayEventConnections; + size_t mDeliveredEvents; +}; + +// --------------------------------------------------------------------------- + +}; // namespace android + +// --------------------------------------------------------------------------- + +#endif /* ANDROID_SURFACE_FLINGER_EVENT_THREAD_H */ diff --git a/services/surfaceflinger/MessageQueue.cpp b/services/surfaceflinger/MessageQueue.cpp index aebe1b82d2..1846ccb316 100644 --- a/services/surfaceflinger/MessageQueue.cpp +++ b/services/surfaceflinger/MessageQueue.cpp @@ -29,167 +29,79 @@ namespace android { // --------------------------------------------------------------------------- -void MessageList::insert(const sp<MessageBase>& node) -{ - LIST::iterator cur(mList.begin()); - LIST::iterator end(mList.end()); - while (cur != end) { - if (*node < **cur) { - mList.insert(cur, node); - return; - } - ++cur; - } - mList.insert(++end, node); +MessageBase::MessageBase() + : MessageHandler() { } -void MessageList::remove(MessageList::LIST::iterator pos) -{ - mList.erase(pos); +MessageBase::~MessageBase() { } +void MessageBase::handleMessage(const Message&) { + this->handler(); + barrier.open(); +}; + // --------------------------------------------------------------------------- MessageQueue::MessageQueue() - : mInvalidate(false) + : mLooper(new Looper(true)), + mInvalidatePending(0) { - mInvalidateMessage = new MessageBase(INVALIDATE); } -MessageQueue::~MessageQueue() -{ +MessageQueue::~MessageQueue() { } -sp<MessageBase> MessageQueue::waitMessage(nsecs_t timeout) -{ - sp<MessageBase> result; - - bool again; +void MessageQueue::waitMessage() { do { - const nsecs_t timeoutTime = systemTime() + timeout; - while (true) { - Mutex::Autolock _l(mLock); - nsecs_t now = systemTime(); - nsecs_t nextEventTime = -1; - - LIST::iterator cur(mMessages.begin()); - if (cur != mMessages.end()) { - result = *cur; - } - - if (result != 0) { - if (result->when <= now) { - // there is a message to deliver - mMessages.remove(cur); - break; - } - nextEventTime = result->when; - result = 0; - } - - // see if we have an invalidate message - if (mInvalidate) { - mInvalidate = false; - mInvalidateMessage->when = now; - result = mInvalidateMessage; - break; - } - - if (timeout >= 0) { - if (timeoutTime < now) { - // we timed-out, return a NULL message - result = 0; - break; - } - if (nextEventTime > 0) { - if (nextEventTime > timeoutTime) { - nextEventTime = timeoutTime; - } - } else { - nextEventTime = timeoutTime; - } - } - - if (nextEventTime >= 0) { - //LOGD("nextEventTime = %lld ms", nextEventTime); - if (nextEventTime > 0) { - // we're about to wait, flush the binder command buffer - IPCThreadState::self()->flushCommands(); - const nsecs_t reltime = nextEventTime - systemTime(); - if (reltime > 0) { - mCondition.waitRelative(mLock, reltime); - } - } - } else { - //LOGD("going to wait"); - // we're about to wait, flush the binder command buffer - IPCThreadState::self()->flushCommands(); - mCondition.wait(mLock); - } - } - // here we're not holding the lock anymore - - if (result == 0) + // handle invalidate events first + if (android_atomic_and(0, &mInvalidatePending) != 0) break; - again = result->handler(); - if (again) { - // the message has been processed. release our reference to it - // without holding the lock. - result->notify(); - result = 0; - } - - } while (again); + IPCThreadState::self()->flushCommands(); - return result; -} + int32_t ret = mLooper->pollOnce(-1); + switch (ret) { + case ALOOPER_POLL_WAKE: + // we got woken-up there is work to do in the main loop + continue; -status_t MessageQueue::postMessage( - const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) -{ - return queueMessage(message, relTime, flags); -} + case ALOOPER_POLL_CALLBACK: + // callback was handled, loop again + continue; -status_t MessageQueue::invalidate() { - Mutex::Autolock _l(mLock); - mInvalidate = true; - mCondition.signal(); - return NO_ERROR; -} + case ALOOPER_POLL_TIMEOUT: + // timeout (should not happen) + continue; -status_t MessageQueue::queueMessage( - const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) -{ - Mutex::Autolock _l(mLock); - message->when = systemTime() + relTime; - mMessages.insert(message); - - //LOGD("MessageQueue::queueMessage time = %lld ms", message->when); - //dumpLocked(message); - - mCondition.signal(); - return NO_ERROR; -} + case ALOOPER_POLL_ERROR: + LOGE("ALOOPER_POLL_ERROR"); + continue; -void MessageQueue::dump(const sp<MessageBase>& message) -{ - Mutex::Autolock _l(mLock); - dumpLocked(message); + default: + // should not happen + LOGE("Looper::pollOnce() returned unknown status %d", ret); + continue; + } + } while (true); } -void MessageQueue::dumpLocked(const sp<MessageBase>& message) +status_t MessageQueue::postMessage( + const sp<MessageBase>& messageHandler, nsecs_t relTime) { - LIST::const_iterator cur(mMessages.begin()); - LIST::const_iterator end(mMessages.end()); - int c = 0; - while (cur != end) { - const char tick = (*cur == message) ? '>' : ' '; - LOGD("%c %d: msg{.what=%08x, when=%lld}", - tick, c, (*cur)->what, (*cur)->when); - ++cur; - c++; + const Message dummyMessage; + if (relTime > 0) { + mLooper->sendMessageDelayed(relTime, messageHandler, dummyMessage); + } else { + mLooper->sendMessage(messageHandler, dummyMessage); } + return NO_ERROR; +} + +status_t MessageQueue::invalidate() { + android_atomic_or(1, &mInvalidatePending); + mLooper->wake(); + return NO_ERROR; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/MessageQueue.h b/services/surfaceflinger/MessageQueue.h index 890f809a3b..25030a6fa8 100644 --- a/services/surfaceflinger/MessageQueue.h +++ b/services/surfaceflinger/MessageQueue.h @@ -23,7 +23,7 @@ #include <utils/threads.h> #include <utils/Timers.h> -#include <utils/List.h> +#include <utils/Looper.h> #include "Barrier.h" @@ -31,92 +31,39 @@ namespace android { // --------------------------------------------------------------------------- -class MessageBase; - -class MessageList -{ - List< sp<MessageBase> > mList; - typedef List< sp<MessageBase> > LIST; -public: - inline LIST::iterator begin() { return mList.begin(); } - inline LIST::const_iterator begin() const { return mList.begin(); } - inline LIST::iterator end() { return mList.end(); } - inline LIST::const_iterator end() const { return mList.end(); } - inline bool isEmpty() const { return mList.empty(); } - void insert(const sp<MessageBase>& node); - void remove(LIST::iterator pos); -}; - -// ============================================================================ - -class MessageBase : - public LightRefBase<MessageBase> +class MessageBase : public MessageHandler { public: - nsecs_t when; - uint32_t what; - int32_t arg0; - - MessageBase() : when(0), what(0), arg0(0) { } - MessageBase(uint32_t what, int32_t arg0=0) - : when(0), what(what), arg0(arg0) { } + MessageBase(); // return true if message has a handler - virtual bool handler() { return false; } + virtual bool handler() = 0; // waits for the handler to be processed void wait() const { barrier.wait(); } - - // releases all waiters. this is done automatically if - // handler returns true - void notify() const { barrier.open(); } protected: - virtual ~MessageBase() { } + virtual ~MessageBase(); private: + virtual void handleMessage(const Message& message); + mutable Barrier barrier; - friend class LightRefBase<MessageBase>; }; -inline bool operator < (const MessageBase& lhs, const MessageBase& rhs) { - return lhs.when < rhs.when; -} - // --------------------------------------------------------------------------- -class MessageQueue -{ - typedef List< sp<MessageBase> > LIST; -public: +class MessageQueue { + sp<Looper> mLooper; + volatile int32_t mInvalidatePending; +public: MessageQueue(); ~MessageQueue(); - // pre-defined messages - enum { - INVALIDATE = '_upd' - }; - - sp<MessageBase> waitMessage(nsecs_t timeout = -1); - - status_t postMessage(const sp<MessageBase>& message, - nsecs_t reltime=0, uint32_t flags = 0); - + void waitMessage(); + status_t postMessage(const sp<MessageBase>& message, nsecs_t reltime=0); status_t invalidate(); - - void dump(const sp<MessageBase>& message); - -private: - status_t queueMessage(const sp<MessageBase>& message, - nsecs_t reltime, uint32_t flags); - void dumpLocked(const sp<MessageBase>& message); - - Mutex mLock; - Condition mCondition; - MessageList mMessages; - bool mInvalidate; - sp<MessageBase> mInvalidateMessage; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 1b00e9380a..d5a8d083e8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -34,6 +34,8 @@ #include <binder/MemoryHeapBase.h> #include <binder/PermissionCache.h> +#include <gui/IDisplayEventConnection.h> + #include <utils/String8.h> #include <utils/String16.h> #include <utils/StopWatch.h> @@ -46,6 +48,8 @@ #include <GLES/gl.h> #include "clz.h" +#include "DisplayEventConnection.h" +#include "EventThread.h" #include "GLExtensions.h" #include "DdmConnection.h" #include "Layer.h" @@ -293,12 +297,16 @@ status_t SurfaceFlinger::readyToRun() // put the origin in the left-bottom corner glOrthof(0, w, 0, h, 0, 1); // l=0, r=w ; b=0, t=h - mReadyToRunBarrier.open(); + + // start the EventThread + mEventThread = new EventThread(this); /* * We're now ready to accept clients... */ + mReadyToRunBarrier.open(); + // start boot animation property_set("ctl.start", "bootanim"); @@ -311,25 +319,30 @@ status_t SurfaceFlinger::readyToRun() #pragma mark Events Handler #endif -void SurfaceFlinger::waitForEvent() -{ - while (true) { - nsecs_t timeout = -1; - sp<MessageBase> msg = mEventQueue.waitMessage(timeout); - if (msg != 0) { - switch (msg->what) { - case MessageQueue::INVALIDATE: - // invalidate message, just return to the main loop - return; - } - } - } +void SurfaceFlinger::waitForEvent() { + mEventQueue.waitMessage(); } void SurfaceFlinger::signalEvent() { mEventQueue.invalidate(); } +status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) { + return mEventQueue.postMessage(msg, reltime); +} + +status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, + nsecs_t reltime, uint32_t flags) { + status_t res = mEventQueue.postMessage(msg, reltime); + if (res == NO_ERROR) { + msg->wait(); + } + return res; +} + +// ---------------------------------------------------------------------------- + bool SurfaceFlinger::authenticateSurfaceTexture( const sp<ISurfaceTexture>& surfaceTexture) const { Mutex::Autolock _l(mStateLock); @@ -371,20 +384,17 @@ bool SurfaceFlinger::authenticateSurfaceTexture( return false; } -status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) -{ - return mEventQueue.postMessage(msg, reltime, flags); +// ---------------------------------------------------------------------------- + +sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() { + sp<DisplayEventConnection> result(new DisplayEventConnection(this)); + mEventThread->registerDisplayEventConnection(result); + return result; } -status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) -{ - status_t res = mEventQueue.postMessage(msg, reltime, flags); - if (res == NO_ERROR) { - msg->wait(); - } - return res; +void SurfaceFlinger::cleanupDisplayEventConnection( + const wp<DisplayEventConnection>& connection) { + mEventThread->unregisterDisplayEventConnection(connection); } // ---------------------------------------------------------------------------- @@ -443,7 +453,7 @@ bool SurfaceFlinger::threadLoop() } else { // pretend we did the post hw.compositionComplete(); - usleep(16667); // 60 fps period + hw.waitForVSync(); } return true; } @@ -1583,9 +1593,16 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) } /* + * VSYNC state + */ + mEventThread->dump(result, buffer, SIZE); + + /* * Dump HWComposer state */ HWComposer& hwc(hw.getHwComposer()); + snprintf(buffer, SIZE, "h/w composer state:\n"); + result.append(buffer); snprintf(buffer, SIZE, " h/w composer %s and %s\n", hwc.initCheck()==NO_ERROR ? "present" : "not present", (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled"); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 17028dbb64..1039f471e5 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -46,6 +46,8 @@ namespace android { class Client; class DisplayHardware; +class DisplayEventConnection; +class EventThread; class Layer; class LayerDim; class LayerScreenshot; @@ -171,6 +173,7 @@ public: int orientation, uint32_t flags); virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags); virtual bool authenticateSurfaceTexture(const sp<ISurfaceTexture>& surface) const; + virtual sp<IDisplayEventConnection> createDisplayEventConnection(); virtual status_t captureScreen(DisplayID dpy, sp<IMemoryHeap>* heap, @@ -222,6 +225,7 @@ private: private: friend class Client; + friend class DisplayEventConnection; friend class LayerBase; friend class LayerBaseClient; friend class Layer; @@ -331,6 +335,9 @@ private: status_t electronBeamOffAnimationImplLocked(); status_t electronBeamOnAnimationImplLocked(); + void cleanupDisplayEventConnection( + const wp<DisplayEventConnection>& connection); + void debugFlashRegions(); void debugShowFPS() const; void drawWormhole() const; @@ -361,6 +368,7 @@ private: GLuint mWormholeTexName; GLuint mProtectedTexName; nsecs_t mBootTime; + sp<EventThread> mEventThread; // Can only accessed from the main thread, these members // don't need synchronization diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp index 4390ca19fc..5020e0007a 100644 --- a/services/surfaceflinger/SurfaceTextureLayer.cpp +++ b/services/surfaceflinger/SurfaceTextureLayer.cpp @@ -28,7 +28,7 @@ namespace android { SurfaceTextureLayer::SurfaceTextureLayer(GLuint tex, const sp<Layer>& layer) - : SurfaceTexture(tex), mLayer(layer) { + : SurfaceTexture(tex, true, GL_TEXTURE_EXTERNAL_OES, false), mLayer(layer) { } SurfaceTextureLayer::~SurfaceTextureLayer() { diff --git a/services/surfaceflinger/tests/vsync/Android.mk b/services/surfaceflinger/tests/vsync/Android.mk new file mode 100644 index 0000000000..9181760453 --- /dev/null +++ b/services/surfaceflinger/tests/vsync/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + vsync.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libui \ + libgui + +LOCAL_MODULE:= test-vsync-events + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp new file mode 100644 index 0000000000..4f79080253 --- /dev/null +++ b/services/surfaceflinger/tests/vsync/vsync.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 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/DisplayEventReceiver.h> +#include <utils/Looper.h> + +using namespace android; + +int receiver(int fd, int events, void* data) +{ + DisplayEventReceiver* q = (DisplayEventReceiver*)data; + + ssize_t n; + DisplayEventReceiver::Event buffer[1]; + + static nsecs_t oldTimeStamp = 0; + + while ((n = q->getEvents(buffer, 1)) > 0) { + for (int i=0 ; i<n ; i++) { + if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { + printf("event vsync: count=%d\t", buffer[i].vsync.count); + } + if (oldTimeStamp) { + float t = float(buffer[i].header.timestamp - oldTimeStamp) / s2ns(1); + printf("%f ms (%f Hz)\n", t*1000, 1.0/t); + } + oldTimeStamp = buffer[i].header.timestamp; + } + } + if (n<0) { + printf("error reading events (%s)\n", strerror(-n)); + } + return 1; +} + +int main(int argc, char** argv) +{ + DisplayEventReceiver myDisplayEvent; + + + sp<Looper> loop = new Looper(false); + loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver, + &myDisplayEvent); + + do { + //printf("about to poll...\n"); + int32_t ret = loop->pollOnce(-1); + switch (ret) { + case ALOOPER_POLL_WAKE: + //("ALOOPER_POLL_WAKE\n"); + break; + case ALOOPER_POLL_CALLBACK: + //("ALOOPER_POLL_CALLBACK\n"); + break; + case ALOOPER_POLL_TIMEOUT: + printf("ALOOPER_POLL_TIMEOUT\n"); + break; + case ALOOPER_POLL_ERROR: + printf("ALOOPER_POLL_TIMEOUT\n"); + break; + default: + printf("ugh? poll returned %d\n", ret); + break; + } + } while (1); + + return 0; +} diff --git a/services/surfaceflinger/tests/waitforvsync/Android.mk b/services/surfaceflinger/tests/waitforvsync/Android.mk new file mode 100644 index 0000000000..c25f5ab083 --- /dev/null +++ b/services/surfaceflinger/tests/waitforvsync/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + waitforvsync.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + +LOCAL_MODULE:= test-waitforvsync + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp b/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp new file mode 100644 index 0000000000..279b88b059 --- /dev/null +++ b/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/fb.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +int main(int argc, char** argv) { + int fd = open("/dev/graphics/fb0", O_RDWR); + if (fd >= 0) { + do { + uint32_t crt = 0; + int err = ioctl(fd, FBIO_WAITFORVSYNC, &crt); + if (err < 0) { + printf("FBIO_WAITFORVSYNC error: %s\n", strerror(errno)); + break; + } + } while(1); + close(fd); + } + return 0; +} |