summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/gui/DisplayEventReceiver.h105
-rw-r--r--include/gui/IDisplayEventConnection.h55
-rw-r--r--include/gui/SurfaceTexture.h30
-rw-r--r--include/private/gui/ComposerService.h53
-rw-r--r--include/surfaceflinger/ISurfaceComposer.h10
-rw-r--r--include/surfaceflinger/SurfaceComposerClient.h22
-rw-r--r--include/ui/GraphicBuffer.h1
-rwxr-xr-xinclude/ui/KeycodeLabels.h4
-rw-r--r--include/utils/BasicHashtable.h393
-rw-r--r--include/utils/TypeHelpers.h44
-rw-r--r--libs/gui/Android.mk6
-rw-r--r--libs/gui/BitTube.cpp5
-rw-r--r--libs/gui/DisplayEventReceiver.cpp83
-rw-r--r--libs/gui/IDisplayEventConnection.cpp73
-rw-r--r--libs/gui/ISurfaceComposer.cpp32
-rw-r--r--libs/gui/SurfaceComposerClient.cpp1
-rw-r--r--libs/gui/SurfaceTexture.cpp419
-rw-r--r--libs/gui/SurfaceTextureClient.cpp2
-rw-r--r--libs/gui/tests/SurfaceTexture_test.cpp611
-rw-r--r--libs/gui/tests/Surface_test.cpp2
-rw-r--r--libs/utils/Android.mk1
-rw-r--r--libs/utils/BasicHashtable.cpp338
-rw-r--r--libs/utils/CallStack.cpp32
-rwxr-xr-xlibs/utils/primes.py47
-rw-r--r--libs/utils/tests/Android.mk3
-rw-r--r--libs/utils/tests/BasicHashtable_test.cpp577
-rw-r--r--opengl/libs/Android.mk9
-rw-r--r--opengl/libs/EGL/eglApi.cpp5
-rw-r--r--opengl/libs/EGL/egl_cache.cpp12
-rw-r--r--services/surfaceflinger/Android.mk28
-rw-r--r--services/surfaceflinger/DisplayEventConnection.cpp62
-rw-r--r--services/surfaceflinger/DisplayEventConnection.h60
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.cpp34
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.h12
-rw-r--r--services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp81
-rw-r--r--services/surfaceflinger/DisplayHardware/VSyncBarrier.h41
-rw-r--r--services/surfaceflinger/EventThread.cpp129
-rw-r--r--services/surfaceflinger/EventThread.h79
-rw-r--r--services/surfaceflinger/MessageQueue.cpp186
-rw-r--r--services/surfaceflinger/MessageQueue.h79
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp71
-rw-r--r--services/surfaceflinger/SurfaceFlinger.h8
-rw-r--r--services/surfaceflinger/SurfaceTextureLayer.cpp2
-rw-r--r--services/surfaceflinger/tests/vsync/Android.mk18
-rw-r--r--services/surfaceflinger/tests/vsync/vsync.cpp81
-rw-r--r--services/surfaceflinger/tests/waitforvsync/Android.mk14
-rw-r--r--services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp45
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(&timestamp) < 0) {
+ // vsync not supported!
+ usleep( getDelayToNextVSyncUs(&timestamp) );
+ }
+ 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;
+}